diff --git a/Cargo.lock b/Cargo.lock index 050722f..3c94c64 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -38,6 +38,15 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + [[package]] name = "bit-vec" version = "0.6.3" @@ -327,7 +336,7 @@ version = "0.110.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a23d09e2994f8696c6a73e16a44500d012ece93085cf44d4d872b82920b8a4c0" dependencies = [ - "ckb-system-scripts 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", + "ckb-system-scripts 0.5.4", "ckb-types", "includedir", "includedir_codegen", @@ -357,6 +366,19 @@ dependencies = [ [[package]] name = "ckb-system-scripts" version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa5c59063142de7a68cfad4449c6b3863563856219a2925dfb8c5f019ec2aa47" +dependencies = [ + "blake2b-rs", + "faster-hex", + "includedir", + "includedir_codegen", + "phf", +] + +[[package]] +name = "ckb-system-scripts" +version = "0.6.0-alpha.1" dependencies = [ "blake2b-rs", "byteorder", @@ -372,25 +394,13 @@ dependencies = [ "includedir_codegen", "lazy_static", "phf", + "proptest", "rand 0.7.3", "ripemd160", "secp256k1 0.15.5", "sha2", ] -[[package]] -name = "ckb-system-scripts" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa5c59063142de7a68cfad4449c6b3863563856219a2925dfb8c5f019ec2aa47" -dependencies = [ - "blake2b-rs", - "faster-hex", - "includedir", - "includedir_codegen", - "phf", -] - [[package]] name = "ckb-traits" version = "0.110.0" @@ -536,6 +546,27 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d978bd5d343e8ab9b5c0fc8d93ff9c602fdc96616ffff9c05ac7a155419b824" +[[package]] +name = "errno" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "fake-simd" version = "0.1.2" @@ -548,6 +579,15 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51e2ce894d53b295cf97b05685aa077950ff3e8541af83217fc720a6437169f8" +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + [[package]] name = "flate2" version = "1.0.26" @@ -558,6 +598,12 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "fuchsia-cprng" version = "0.1.1" @@ -581,7 +627,18 @@ checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" dependencies = [ "cfg-if", "libc", - "wasi", + "wasi 0.9.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", ] [[package]] @@ -624,6 +681,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "hermit-abi" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" + [[package]] name = "includedir" version = "0.6.0" @@ -645,6 +708,26 @@ dependencies = [ "walkdir", ] +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "io-lifetimes" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys", +] + [[package]] name = "itoa" version = "1.0.8" @@ -663,6 +746,12 @@ version = "0.2.147" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +[[package]] +name = "libm" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" + [[package]] name = "linked-hash-map" version = "0.5.6" @@ -672,6 +761,12 @@ dependencies = [ "serde", ] +[[package]] +name = "linux-raw-sys" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" + [[package]] name = "lock_api" version = "0.4.10" @@ -723,6 +818,16 @@ dependencies = [ "faster-hex", ] +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg 1.1.0", + "libm", +] + [[package]] name = "numext-constructor" version = "0.1.6" @@ -863,6 +968,32 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "proptest" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e35c06b98bf36aba164cc17cb25f7e232f5c4aeea73baa14b8a9f0d92dbfa65" +dependencies = [ + "bit-set", + "bitflags", + "byteorder", + "lazy_static", + "num-traits", + "rand 0.8.5", + "rand_chacha 0.3.1", + "rand_xorshift 0.3.0", + "regex-syntax 0.6.29", + "rusty-fork", + "tempfile", + "unarray", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + [[package]] name = "quote" version = "1.0.29" @@ -887,7 +1018,7 @@ dependencies = [ "rand_jitter", "rand_os", "rand_pcg 0.1.2", - "rand_xorshift", + "rand_xorshift 0.1.1", "winapi", ] @@ -897,7 +1028,7 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" dependencies = [ - "getrandom", + "getrandom 0.1.16", "libc", "rand_chacha 0.2.2", "rand_core 0.5.1", @@ -905,6 +1036,17 @@ dependencies = [ "rand_pcg 0.2.1", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + [[package]] name = "rand_chacha" version = "0.1.1" @@ -925,6 +1067,16 @@ dependencies = [ "rand_core 0.5.1", ] +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + [[package]] name = "rand_core" version = "0.3.1" @@ -946,7 +1098,16 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" dependencies = [ - "getrandom", + "getrandom 0.1.16", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.10", ] [[package]] @@ -1029,6 +1190,15 @@ dependencies = [ "rand_core 0.3.1", ] +[[package]] +name = "rand_xorshift" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +dependencies = [ + "rand_core 0.6.4", +] + [[package]] name = "rdrand" version = "0.4.0" @@ -1056,7 +1226,7 @@ dependencies = [ "aho-corasick", "memchr", "regex-automata", - "regex-syntax", + "regex-syntax 0.7.3", ] [[package]] @@ -1067,9 +1237,15 @@ checksum = "fa250384981ea14565685dea16a9ccc4d1c541a13f82b9c168572264d1df8c56" dependencies = [ "aho-corasick", "memchr", - "regex-syntax", + "regex-syntax 0.7.3", ] +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + [[package]] name = "regex-syntax" version = "0.7.3" @@ -1096,6 +1272,32 @@ dependencies = [ "semver", ] +[[package]] +name = "rustix" +version = "0.37.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "rusty-fork" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", +] + [[package]] name = "ryu" version = "1.0.14" @@ -1248,6 +1450,20 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tempfile" +version = "3.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31c0432476357e58790aaa47a8efb0c5138f137343f3b5f23bd36a27e3b0a6d6" +dependencies = [ + "autocfg 1.1.0", + "cfg-if", + "fastrand", + "redox_syscall", + "rustix", + "windows-sys", +] + [[package]] name = "thiserror" version = "1.0.41" @@ -1283,12 +1499,27 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + [[package]] name = "unicode-ident" version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22049a19f4a68748a168c0fc439f9516686aa045927ff767eca0a85101fb6e73" +[[package]] +name = "wait-timeout" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +dependencies = [ + "libc", +] + [[package]] name = "walkdir" version = "2.3.3" @@ -1305,6 +1536,12 @@ version = "0.9.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + [[package]] name = "winapi" version = "0.3.9" @@ -1336,6 +1573,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-targets" version = "0.48.1" diff --git a/Cargo.toml b/Cargo.toml index 2c98d3d..c91653f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ckb-system-scripts" -version = "0.5.4" +version = "0.6.0-alpha.1" authors = ["Nervos Core Dev "] edition = "2018" build = "build.rs" @@ -33,3 +33,4 @@ ripemd160 = "0.8.0" sha2 = "0.8.0" secp256k1 = { version = "0.15.1" } faster-hex = "0.6.0" +proptest = "1.2.0" diff --git a/build.rs b/build.rs index b83d6a4..8bf13e5 100644 --- a/build.rs +++ b/build.rs @@ -23,7 +23,7 @@ const BINARIES: &[(&str, &str)] = &[ ), ( "dao", - "2f7e76d1a866f7a064e251bf4f2b212a28b532dd6df19a87011784bdbe69726b", + "af4e7bd53bb65becbea8131d460589c6a5f4c8be4ba77badba367c5e7c3eee24", ), ( "secp256k1_blake160_multisig_all", diff --git a/c/dao.c b/c/dao.c index a008b21..3d31475 100644 --- a/c/dao.c +++ b/c/dao.c @@ -1,11 +1,13 @@ // # DAO // -// This file provides NervosDAO on chain script implementation. It is designed to -// work as the type script of a cell. Please refer to [Nervos DAO RFC](https://github.com/nervosnetwork/rfcs/blob/master/rfcs/0023-dao-deposit-withdraw/0023-dao-deposit-withdraw.md) +// This file provides NervosDAO on chain script implementation. It is designed +// to work as the type script of a cell. Please refer to [Nervos DAO +// RFC](https://github.com/nervosnetwork/rfcs/blob/master/rfcs/0023-dao-deposit-withdraw/0023-dao-deposit-withdraw.md) // on more details. // Necessary headers. This script will need to perform syscalls to read current -// transaction structure, then parse WitnessArgs data structure in molecule format. +// transaction structure, then parse WitnessArgs data structure in molecule +// format. #include "ckb_syscalls.h" #include "protocol.h" @@ -15,7 +17,6 @@ #define ERROR_SYSCALL -4 #define ERROR_BUFFER_NOT_ENOUGH -10 #define ERROR_ENCODING -11 -#define ERROR_WITNESS_TOO_LONG -12 #define ERROR_OVERFLOW -13 #define ERROR_INVALID_WITHDRAW_BLOCK -14 #define ERROR_INCORRECT_CAPACITY -15 @@ -33,13 +34,19 @@ #error "Exhausted marker cannot be the same as CKB_INDEX_OUT_OF_BOUND!" #endif -// Common definitions here, one important limitation, is that this script only works -// with scripts and witnesses that are no larger than 32KB. We believe this should be enough -// for most cases. +#define CWHR_DEBUG(...) +#define CWHR_ERROR_CODE -22 +#include "witness_args_handwritten_reader.h" + #define HASH_SIZE 32 #define HEADER_SIZE 4096 -/* 32 KB */ -#define MAX_WITNESS_SIZE 32768 +// Note witness will be loaded via a reader that supports arbitrary length. The +// 32KB size here is merely the temporary buffer used to hold part of witness, it +// does not necessary limit the total witness size to 32KB. +#define WITNESS_BUF_SIZE 32768 +// The only script that will be loaded by Nervos DAO contract, is Nervos DAO type +// script itself. This type script has empty args, which means the type script will +// never exceed 32K. We are safe keeping this value here. #define SCRIPT_SIZE 32768 // One lock period of NervosDAO is set as 180 epochs, which is roughly 30 days. @@ -62,39 +69,40 @@ // WitnessArgs. The value is kept as a 64-bit unsigned little endian value. 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]; + unsigned char witness[WITNESS_BUF_SIZE]; - len = MAX_WITNESS_SIZE; - ret = ckb_load_witness(witness, &len, 0, input_index, CKB_SOURCE_INPUT); + cwhr_cursor_t cursor; + ret = cwhr_cursor_initialize( + &cursor, cwhr_witness_loader_create(input_index, CKB_SOURCE_INPUT), + witness, WITNESS_BUF_SIZE); if (ret != CKB_SUCCESS) { - return ERROR_SYSCALL; + return ret; } - if (len > MAX_WITNESS_SIZE) { - return ERROR_WITNESS_TOO_LONG; + cwhr_witness_args_reader_t reader; + ret = cwhr_witness_args_reader_create(&reader, &cursor); + if (ret != CKB_SUCCESS) { + return ret; } - - mol_seg_t witness_seg; - witness_seg.ptr = (uint8_t *)witness; - witness_seg.size = len; - - if (MolReader_WitnessArgs_verify(&witness_seg, false) != MOL_OK) { + ret = cwhr_witness_args_reader_verify(&reader, 0); + if (ret != CKB_SUCCESS) { return ERROR_ENCODING; } - // Load `input_type` - mol_seg_t type_seg = MolReader_WitnessArgs_get_input_type(&witness_seg); - if (MolReader_BytesOpt_is_none(&type_seg)) { + if (!cwhr_witness_args_reader_has_input_type(&reader)) { return ERROR_ENCODING; } - mol_seg_t type_bytes_seg = MolReader_Bytes_raw_bytes(&type_seg); - if (type_bytes_seg.size != 8) { + cwhr_bytes_reader_t input_type; + ret = cwhr_witness_args_reader_input_type(&reader, &input_type); + if (ret != CKB_SUCCESS) { + return ret; + } + + if (cwhr_bytes_reader_length(&input_type) != 8) { return ERROR_ENCODING; } - *index = *type_bytes_seg.ptr; - return CKB_SUCCESS; + return cwhr_bytes_reader_memcpy(&input_type, index); } // Parses epoch info from the epoch field in block header. @@ -166,12 +174,14 @@ static int load_dao_header_data(size_t index, size_t source, // Validates an input cell is indeed deposited to NervosDAO in // `deposited_block_number`, then calculates the capacity one can withdraw from -// this deposited cell. The function will tries to first read an index value from -// the witness of the position as provided input cell index. Then use the read -// index value as an index into `header_deps` section of current transaction for -// a header. The header is then used as withdraw header to calculate deposit period. +// this deposited cell. The function will tries to first read an index value +// from the witness of the position as provided input cell index. Then use the +// read index value as an index into `header_deps` section of current +// transaction for a header. The header is then used as withdraw header to +// calculate deposit period. static int calculate_dao_input_capacity(size_t input_index, uint64_t deposited_block_number, + uint64_t deposited_occupied_capacity, uint64_t original_capacity, uint64_t *calculated_capacity) { uint64_t len = 0; @@ -230,19 +240,19 @@ static int calculate_dao_input_capacity(size_t input_index, deposited_epochs++; } uint64_t lock_epochs = (deposited_epochs + (LOCK_PERIOD_EPOCHS - 1)) / - LOCK_PERIOD_EPOCHS * LOCK_PERIOD_EPOCHS; + LOCK_PERIOD_EPOCHS * LOCK_PERIOD_EPOCHS; // Cell must at least be locked for one full lock period(180 epochs) if (lock_epochs < LOCK_PERIOD_EPOCHS) { return ERROR_INVALID_WITHDRAW_BLOCK; } // Since actually just stores an epoch integer with a fraction part, it is // not necessary a valid epoch number with fraction. - uint64_t minimal_since_epoch_number = - deposit_data.epoch_number + lock_epochs; + uint64_t minimal_since_epoch_number = deposit_data.epoch_number + lock_epochs; uint64_t minimal_since_epoch_index = deposit_data.epoch_index; uint64_t minimal_since_epoch_length = deposit_data.epoch_length; - // Loads since value from current input to make sure correct lock period is set. + // Loads since value from current input to make sure correct lock period is + // set. uint64_t input_since = 0; len = 8; ret = ckb_load_input_by_field(((unsigned char *)&input_since), &len, 0, @@ -279,28 +289,14 @@ static int calculate_dao_input_capacity(size_t input_index, return ERROR_INCORRECT_SINCE; } - // Now we can calculate the maximum amount one can withdraw from this cell. Please - // refer to Nervos DAO RFC for more details on the formula used here. + // Now we can calculate the maximum amount one can withdraw from this cell. + // Please refer to Nervos DAO RFC for more details on the formula used here. uint64_t deposit_accumulate_rate = *((uint64_t *)(&deposit_data.dao[8])); uint64_t withdraw_accumulate_rate = *((uint64_t *)(&withdraw_data.dao[8])); - // Nervos DAO interest is only calculated on *occupied capacity*, which means all - // capacities that are not used as storage cost in a cell. - uint64_t occupied_capacity = 0; - len = 8; - ret = ckb_load_cell_by_field(((unsigned char *)&occupied_capacity), &len, 0, - input_index, CKB_SOURCE_INPUT, - CKB_CELL_FIELD_OCCUPIED_CAPACITY); - if (ret != CKB_SUCCESS) { - return ERROR_SYSCALL; - } - if (len != 8) { - return ERROR_SYSCALL; - } - // Like any serious smart contracts, we will perform overflow checks here. uint64_t counted_capacity = 0; - if (__builtin_usubl_overflow(original_capacity, occupied_capacity, + if (__builtin_usubl_overflow(original_capacity, deposited_occupied_capacity, &counted_capacity)) { return ERROR_OVERFLOW; } @@ -310,7 +306,7 @@ static int calculate_dao_input_capacity(size_t input_index, ((__int128)deposit_accumulate_rate); uint64_t withdraw_capacity = 0; - if (__builtin_uaddl_overflow(occupied_capacity, + if (__builtin_uaddl_overflow(deposited_occupied_capacity, (uint64_t)withdraw_counted_capacity, &withdraw_capacity)) { return ERROR_OVERFLOW; @@ -341,7 +337,7 @@ static int validate_withdrawing_cell(size_t index, uint64_t input_capacity, // Check type script len = HASH_SIZE; int ret = ckb_load_cell_by_field(hash1, &len, 0, index, CKB_SOURCE_OUTPUT, - CKB_CELL_FIELD_TYPE_HASH); + CKB_CELL_FIELD_TYPE_HASH); if (ret != CKB_SUCCESS) { return ret; } @@ -372,30 +368,63 @@ static int validate_withdrawing_cell(size_t index, uint64_t input_capacity, 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); + uint8_t output_data[16]; + len = 16; + ret = ckb_load_cell_data(output_data, &len, 0, index, CKB_SOURCE_OUTPUT); if (ret != CKB_SUCCESS) { return ret; } - if (len != 8) { + if (len != 8 && len != 16) { return ERROR_SYSCALL; } + uint64_t stored_block_number = *((uint64_t *)output_data); if (stored_block_number != deposit_header.block_number) { return ERROR_INVALID_WITHDRAWING_CELL; } + if (len == 16) { + uint64_t stored_occupied_capacity = *((uint64_t *)(&output_data[8])); + uint64_t actual_occupied_capacity = 0; + len = 8; + ret = ckb_load_cell_by_field((unsigned char *)&actual_occupied_capacity, + &len, 0, index, CKB_SOURCE_INPUT, + CKB_CELL_FIELD_OCCUPIED_CAPACITY); + if (ret != CKB_SUCCESS) { + return ret; + } + if (len != 8) { + return ERROR_SYSCALL; + } + if (stored_occupied_capacity != actual_occupied_capacity) { + return ERROR_INVALID_WITHDRAWING_CELL; + } + } else { + uint64_t input_lock_length = 0; + ret = ckb_load_cell_by_field(NULL, &input_lock_length, 0, index, + CKB_SOURCE_INPUT, CKB_CELL_FIELD_LOCK); + if (ret != CKB_SUCCESS) { + return ret; + } + uint64_t output_lock_length = 0; + ret = ckb_load_cell_by_field(NULL, &output_lock_length, 0, index, + CKB_SOURCE_OUTPUT, CKB_CELL_FIELD_LOCK); + if (ret != CKB_SUCCESS) { + return ret; + } + if (input_lock_length != output_lock_length) { + return ERROR_INVALID_WITHDRAWING_CELL; + } + } return CKB_SUCCESS; } -static int validate_input(size_t index, uint64_t *input_capacities, +static int validate_input(size_t index, uint64_t *input_capacities, uint64_t *output_withdrawing, unsigned char *script_hash) { int dao_input = 0; uint64_t capacity = 0; uint64_t len = 8; int ret = ckb_load_cell_by_field(((unsigned char *)&capacity), &len, 0, index, - CKB_SOURCE_INPUT, CKB_CELL_FIELD_CAPACITY); + CKB_SOURCE_INPUT, CKB_CELL_FIELD_CAPACITY); if (ret == CKB_INDEX_OUT_OF_BOUND) { return ERROR_MARKER_EXHAUSTED; } else if (ret == CKB_SUCCESS) { @@ -405,7 +434,7 @@ static int validate_input(size_t index, uint64_t *input_capacities, unsigned char current_script_hash[HASH_SIZE]; len = HASH_SIZE; ret = ckb_load_cell_by_field(current_script_hash, &len, 0, index, - CKB_SOURCE_INPUT, CKB_CELL_FIELD_TYPE_HASH); + CKB_SOURCE_INPUT, CKB_CELL_FIELD_TYPE_HASH); // When an input cell has the same type script hash as current running // we know we are dealing with a script using NervosDAO script. if ((ret == CKB_SUCCESS) && len == HASH_SIZE && @@ -419,49 +448,69 @@ static int validate_input(size_t index, uint64_t *input_capacities, if (!dao_input) { // Normal input, use its own capacity if (__builtin_uaddl_overflow(*input_capacities, capacity, - input_capacities)) { + input_capacities)) { return ERROR_OVERFLOW; } } else { + uint8_t data[16]; + len = 16; + ret = ckb_load_cell_data(data, &len, 0, index, CKB_SOURCE_INPUT); + if (ret != CKB_SUCCESS) { + return ret; + } + if (len != 8 && len != 16) { + return ERROR_SYSCALL; + } // In a Nervos DAO transaction, we might have 2 types of input cells using // Nervos DAO type script: // // * A deposited cell // * A withdrawing cell // - // If you are also looking at the Nervos DAO RFC, a deposited cell is created in - // the initial deposit phase, and spent in withdraw phase 1; a withdrawing cell - // is created in withdraw phase 1, then spent in withdraw phase 2. + // If you are also looking at the Nervos DAO RFC, a deposited cell is + // created in the initial deposit phase, and spent in withdraw phase 1; a + // withdrawing cell is created in withdraw phase 1, then spent in withdraw + // phase 2. // - // The way to tell them apart, is that a deposited cell always contains 8 bytes - // of 0 as cell data, while a withdrawing cell would contain a positive number - // denoting the original deposited block number. - 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; - } + // The way to tell them apart, is that a deposited cell always contains 8 + // bytes of 0 as cell data, while a withdrawing cell would contain a + // positive number denoting the original deposited block number. + uint64_t block_number = *((uint64_t *)data); if (block_number > 0) { + uint64_t occupied_capacity = 0xFFFFFFFFFFFFFFFF; + if (len == 16) { + occupied_capacity = *((uint64_t *)(&data[8])); + } else { + len = 8; + ret = ckb_load_cell_by_field(((unsigned char *)&occupied_capacity), + &len, 0, index, CKB_SOURCE_INPUT, + CKB_CELL_FIELD_OCCUPIED_CAPACITY); + if (ret != CKB_SUCCESS) { + return ERROR_SYSCALL; + } + if (len != 8) { + return ERROR_SYSCALL; + } + } + // For a withdrawing cell, we can start calculate the maximum capacity // that one can withdraw from it. uint64_t dao_capacity = 0; - ret = calculate_dao_input_capacity(index, block_number, capacity, - &dao_capacity); + ret = calculate_dao_input_capacity(index, block_number, occupied_capacity, + capacity, &dao_capacity); if (ret != CKB_SUCCESS) { return ret; } // Like any serious smart contracts, we will perform overflow checks here. if (__builtin_uaddl_overflow(*input_capacities, dao_capacity, - input_capacities)) { + input_capacities)) { return ERROR_OVERFLOW; } } else { + if (len != 8) { + return ERROR_SYSCALL; + } // For a deposited cell, we only need to check that 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. @@ -476,7 +525,7 @@ static int validate_input(size_t index, uint64_t *input_capacities, *output_withdrawing = 1; // Like any serious smart contracts, we will perform overflow checks here. if (__builtin_uaddl_overflow(*input_capacities, capacity, - input_capacities)) { + input_capacities)) { return ERROR_OVERFLOW; } } @@ -485,13 +534,13 @@ static int validate_input(size_t index, uint64_t *input_capacities, return 0; } -static int validate_output(size_t index, uint64_t *output_capacities, - uint64_t output_withdrawing, - unsigned char *script_hash) { +static int validate_output(size_t index, uint64_t *output_capacities, + uint64_t output_withdrawing, + unsigned char *script_hash) { uint64_t capacity = 0; uint64_t len = 8; int ret = ckb_load_cell_by_field(((unsigned char *)&capacity), &len, 0, index, - CKB_SOURCE_OUTPUT, CKB_CELL_FIELD_CAPACITY); + CKB_SOURCE_OUTPUT, CKB_CELL_FIELD_CAPACITY); if (ret == CKB_INDEX_OUT_OF_BOUND) { return ERROR_MARKER_EXHAUSTED; } @@ -504,14 +553,14 @@ static int validate_output(size_t index, uint64_t *output_capacities, // Like any serious smart contracts, we will perform overflow checks here. if (__builtin_uaddl_overflow(*output_capacities, capacity, - output_capacities)) { + 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); + CKB_SOURCE_OUTPUT, CKB_CELL_FIELD_TYPE_HASH); if ((ret == CKB_SUCCESS) && len == HASH_SIZE && (memcmp(script_hash, current_script_hash, HASH_SIZE) == 0)) { // Similarly to input, we also need to check if we are creating a @@ -529,7 +578,7 @@ static int validate_output(size_t index, uint64_t *output_capacities, uint64_t block_number = 0; len = 8; ret = ckb_load_cell_data((unsigned char *)&block_number, &len, 0, index, - CKB_SOURCE_OUTPUT); + CKB_SOURCE_OUTPUT); if (ret != CKB_SUCCESS) { return ret; } @@ -544,7 +593,6 @@ static int validate_output(size_t index, uint64_t *output_capacities, return 0; } - int main() { int ret; unsigned char script_hash[HASH_SIZE]; @@ -554,9 +602,9 @@ int main() { mol_seg_t args_seg; mol_seg_t bytes_seg; - // NervosDAO script requires script args part to be empty, this way we can ensure - // that all DAO related scripts in a transaction is mapped to the same group, and - // processed together in one execution. + // NervosDAO script requires script args part to be empty, this way we can + // ensure that all DAO related scripts in a transaction is mapped to the same + // group, and processed together in one execution. len = SCRIPT_SIZE; ret = ckb_load_script(script, &len, 0); if (ret != CKB_SUCCESS) { @@ -608,24 +656,26 @@ int main() { uint64_t input_exhausted = 0; uint64_t output_exhausted = 0; while (!(input_exhausted && output_exhausted)) { - // Reset the flag to ensure that it does not retain the value from the last iteration when - // inputs have been exhausted. + // Reset the flag to ensure that it does not retain the value from the last + // iteration when inputs have been exhausted. output_withdrawing = 0; if (!input_exhausted) { - ret = validate_input(index, &input_capacities, &output_withdrawing, script_hash); + ret = validate_input(index, &input_capacities, &output_withdrawing, + script_hash); if (ret == ERROR_MARKER_EXHAUSTED) { input_exhausted = 1; - } else if (ret != CKB_SUCCESS ){ + } else if (ret != CKB_SUCCESS) { return ret; } } if (!output_exhausted) { - ret = validate_output(index, &output_capacities, output_withdrawing, script_hash); + ret = validate_output(index, &output_capacities, output_withdrawing, + script_hash); if (ret == ERROR_MARKER_EXHAUSTED) { output_exhausted = 1; - } else if (ret != CKB_SUCCESS ){ + } else if (ret != CKB_SUCCESS) { return ret; } } @@ -633,9 +683,9 @@ int main() { index += 1; } - // The final thing we need to check here, is that the sum of capacities in output - // cells, cannot exceed the sum of capacities in all input cells with Nervos DAO - // issuance considered. + // The final thing we need to check here, is that the sum of capacities in + // output cells, cannot exceed the sum of capacities in all input cells with + // Nervos DAO issuance considered. if (output_capacities > input_capacities) { return ERROR_INCORRECT_CAPACITY; } diff --git a/c/witness_args_handwritten_reader.h b/c/witness_args_handwritten_reader.h new file mode 100644 index 0000000..a5a195d --- /dev/null +++ b/c/witness_args_handwritten_reader.h @@ -0,0 +1,473 @@ +// This file is taken from: +// https://github.com/xxuejie/ckb-witness-args-handwritten-reader/blob/ac2008fc0c04e0bb44938ca58b7647e03aa90752/c/witness_args_handwritten_reader.h + +// This is a handwritten WitnessArgs validator & reader supporting +// witnesses of arbitrary length. It employs a cursor based design +// that dynamically loads data when necessary. For now, this is +// handwritten which only deals with WitnessArgs data structure, +// later we might expand the same idea into molecule, so we can generate +// similar reader on any molecule schema. + +#ifndef CKB_WITNESS_ARGS_HANDWRITTEN_READER_ +#define CKB_WITNESS_ARGS_HANDWRITTEN_READER_ + +#include "ckb_syscalls.h" + +#ifndef CWHR_DEBUG +/* + * This is a poor-man's debugging function, ideally one would want to + * Use ckb_printf from ckb-c-stdlib + */ +int _cwhr_printf(const char *format, ...); +#define CWHR_DEBUG(...) _cwhr_printf(__VA_ARGS__) +#endif + +#ifndef CWHR_ERROR_CODE +#define CWHR_ERROR_CODE -40 +#endif + +#include +#include +#include + +typedef int (*cwhr_data_accessor_f)(const uint8_t *data, size_t length, + void *context); + +typedef struct { + uint8_t *buf; + size_t offset; +} cwhr_memcpy_accessor_context; + +int cwhr_memcpy_accessor_context_initialize( + cwhr_memcpy_accessor_context *context, void *buf) { + context->buf = (uint8_t *)buf; + context->offset = 0; + return CKB_SUCCESS; +} + +int cwhr_memcpy_accessor(const uint8_t *data, size_t length, void *context) { + cwhr_memcpy_accessor_context *c = (cwhr_memcpy_accessor_context *)context; + memcpy(&c->buf[c->offset], data, length); + c->offset += length; + return CKB_SUCCESS; +} + +typedef struct { + size_t syscall; + size_t payload3; + size_t payload4; + size_t payload5; +} cwhr_loader_t; + +cwhr_loader_t cwhr_witness_loader_create(size_t index, size_t source) { + cwhr_loader_t result; + result.syscall = SYS_ckb_load_witness; + result.payload3 = index; + result.payload4 = source; + result.payload5 = 0; + return result; +} + +int cwhr_loader_load(const cwhr_loader_t *loader, void *addr, uint64_t *len, + size_t offset) { + volatile uint64_t inner_len = *len; + int ret = syscall(loader->syscall, addr, &inner_len, offset, loader->payload3, + loader->payload4, loader->payload5); + *len = inner_len; + return ret; +} + +typedef struct { + uint8_t *buf; + size_t length; + + size_t loaded_offset; + size_t loaded_length; + size_t total_length; + + cwhr_loader_t loader; +} cwhr_cursor_t; + +#define CWHR_MINIMAL_BUFFER_LENGTH 32 + +int cwhr_cursor_initialize(cwhr_cursor_t *cursor, cwhr_loader_t loader, + uint8_t *buf, size_t length) { + if (length < CWHR_MINIMAL_BUFFER_LENGTH) { + CWHR_DEBUG("Provided buffer is too small for cursor!\n"); + return CWHR_ERROR_CODE; + } + + size_t total_length = length; + int ret = cwhr_loader_load(&loader, buf, &total_length, 0); + if (ret != CKB_SUCCESS) { + return ret; + } + + cursor->buf = buf; + cursor->length = length; + cursor->loaded_offset = 0; + cursor->loaded_length = total_length; + if (cursor->loaded_length > length) { + cursor->loaded_length = length; + } + cursor->total_length = total_length; + cursor->loader = loader; + + return CKB_SUCCESS; +} + +int cwhr_cursor_shift(cwhr_cursor_t *cursor, size_t offset) { + cursor->loaded_length = cursor->length; + int ret = cwhr_loader_load(&cursor->loader, cursor->buf, + &cursor->loaded_length, offset); + if (ret != CKB_SUCCESS) { + return ret; + } + if (cursor->loaded_length > cursor->length) { + cursor->loaded_length = cursor->length; + } + return CKB_SUCCESS; +} + +const uint8_t *cwhr_cursor_read_available(cwhr_cursor_t *cursor, size_t offset, + size_t minimal_length, + size_t *available_length) { + size_t loaded_end = cursor->loaded_offset + cursor->loaded_length; + if (offset >= cursor->loaded_offset && offset <= loaded_end && + (offset + minimal_length) <= loaded_end) { + size_t start = offset - cursor->loaded_offset; + *available_length = cursor->loaded_length - start; + return &cursor->buf[start]; + } + if (minimal_length > cursor->length) { + CWHR_DEBUG("Requesting length is larger than buffer length!\n"); + return NULL; + } + int ret = cwhr_cursor_shift(cursor, offset); + if (ret != CKB_SUCCESS) { + CWHR_DEBUG("Cursor shift error: %d\n", ret); + return NULL; + } + *available_length = cursor->loaded_length; + return cursor->buf; +} + +int cwhr_cursor_read_u32(cwhr_cursor_t *cursor, size_t offset, + uint32_t *result) { + size_t available_length = 0; + const uint8_t *p = + cwhr_cursor_read_available(cursor, offset, 4, &available_length); + if (p == NULL) { + return CWHR_ERROR_CODE; + } + if (available_length < 4) { + CWHR_DEBUG("Cursor does not have enough data for a u32!\n"); + return CWHR_ERROR_CODE; + } + *result = *((const uint32_t *)p); + return CKB_SUCCESS; +} + +int cwhr_cursor_read(cwhr_cursor_t *cursor, cwhr_data_accessor_f accessor, + void *context) { + size_t read = 0; + while (read < cursor->total_length) { + size_t available_length = 0; + const uint8_t *p = + cwhr_cursor_read_available(cursor, read, 1, &available_length); + int ret = accessor(p, available_length, context); + if (ret != CKB_SUCCESS) { + CWHR_DEBUG("User-level accessor failure!\n"); + return ret; + } + read += available_length; + } + return CKB_SUCCESS; +} + +int cwhr_cursor_memcpy(cwhr_cursor_t *cursor, void *buf) { + cwhr_memcpy_accessor_context context; + cwhr_memcpy_accessor_context_initialize(&context, buf); + return cwhr_cursor_read(cursor, cwhr_memcpy_accessor, &context); +} + +typedef struct { + cwhr_cursor_t *cursor; + + /* + * When working with a WitnessArgs, we never directly instantiate a + * Bytes structure, we always create a Bytes structure when accessing + * the lock, input_type, or output_type field off the upper level struct. + * Using lock field as an example, given a WitnessArgs structure, the lock + * field can always be read at a certain offset of the WitnessArgs structure. + * Hence we can leverage an optimization here: when accessing the lock field + * in a WitnessArgs structure, a Bytes structure would then created that + * shares the same cursor as the upper-level WitnessArgs, however, the Bytes + * structure would maintain a particular *base_offset* that indicates the + * offset within WitnessArgs, where we can start reading the Bytes structure. + * This way we can reduce syscalls as much as possible: when the cursor for + * WitnessArgs already contains the same data, Bytes can reuse the same data. + * What's more, given a relatively large cursor buffer(32K for example), a + * majority of witnesses can be loaded via a single syscall, while maintaining + * a unified API for dealing with larger witnesses. + */ + size_t base_offset; + size_t length; +} cwhr_bytes_reader_t; + +/* + * Note the different between create and initialize: + * * Create builds a top-level structure, such as Transaction orWitnessArgs. + * It is typically combined with a fresh cursor directly, *base_offset* is + * assumed to be zero, *length* would contain the total length of a cursor; + * * Initialize builds a structure when accessing a field from a upper-level + * structure. For instance, we initialize a Bytes structure when accessing + * the lock field of a WitnessArgs structure, or we initialize a WitnessArgs + * structure when accessing the witness for a particular transaction. The + * *base_offset* here typically has a non-zero value, the *length* field + * is also likely not the full length of the cursor. + */ +int cwhr_bytes_reader_initialize(cwhr_bytes_reader_t *reader, + cwhr_cursor_t *cursor, size_t base_offset, + size_t length) { + reader->cursor = cursor; + reader->base_offset = base_offset; + reader->length = length; + + return CKB_SUCCESS; +} + +int cwhr_bytes_reader_create(cwhr_bytes_reader_t *reader, + cwhr_cursor_t *cursor) { + return cwhr_bytes_reader_initialize(reader, cursor, 0, cursor->total_length); +} + +int cwhr_bytes_reader_verify(cwhr_bytes_reader_t *reader, int compatible) { + (void)compatible; + if (reader->length < 4) { + CWHR_DEBUG("Bytes must have room for length!\n"); + return CWHR_ERROR_CODE; + } + uint32_t count = 0xFFFFFFFF; + int ret = cwhr_cursor_read_u32(reader->cursor, reader->base_offset, &count); + if (ret != CKB_SUCCESS) { + return ret; + } + size_t expected_length = 4 + (size_t)count; + if (reader->length != expected_length) { + CWHR_DEBUG("Bytes has incorrect length! Expected: %ld, actual: %ld\n", + expected_length, reader->length); + return CWHR_ERROR_CODE; + } + return CKB_SUCCESS; +} + +uint32_t cwhr_bytes_reader_length(cwhr_bytes_reader_t *reader) { + return reader->length - 4; +} + +int cwhr_bytes_reader_read(cwhr_bytes_reader_t *reader, + cwhr_data_accessor_f accessor, void *context) { + uint32_t read = 0; + uint32_t length = cwhr_bytes_reader_length(reader); + while (read < length) { + size_t available_length = 0; + const uint8_t *p = cwhr_cursor_read_available( + reader->cursor, reader->base_offset + 4 + read, 1, &available_length); + uint32_t left = length - read; + uint32_t available = (uint32_t)available_length; + if (available > left) { + available = left; + } + int ret = accessor(p, available, context); + if (ret != CKB_SUCCESS) { + CWHR_DEBUG("User-level accessor failure!\n"); + return ret; + } + read += available; + } + return CKB_SUCCESS; +} + +int cwhr_bytes_reader_memcpy(cwhr_bytes_reader_t *reader, void *buf) { + cwhr_memcpy_accessor_context context; + cwhr_memcpy_accessor_context_initialize(&context, buf); + return cwhr_bytes_reader_read(reader, cwhr_memcpy_accessor, &context); +} + +typedef struct { + cwhr_cursor_t *cursor; + + size_t base_offset; + size_t length; + + // WitnessArgs would pre-load some offsets to speed-up accessors. + uint32_t lock_offset; + uint32_t input_type_offset; + uint32_t output_type_offset; +} cwhr_witness_args_reader_t; + +int cwhr_witness_args_reader_initialize(cwhr_witness_args_reader_t *reader, + cwhr_cursor_t *cursor, + size_t base_offset, size_t length) { + reader->cursor = cursor; + reader->base_offset = base_offset; + reader->length = length; + + if (reader->length < 16) { + CWHR_DEBUG("WitnessArgs must have room for length and offsets!\n"); + return CWHR_ERROR_CODE; + } + int ret = cwhr_cursor_read_u32(reader->cursor, reader->base_offset + 4, + &reader->lock_offset); + if (ret != CKB_SUCCESS) { + return ret; + } + ret = cwhr_cursor_read_u32(reader->cursor, reader->base_offset + 8, + &reader->input_type_offset); + if (ret != CKB_SUCCESS) { + return ret; + } + ret = cwhr_cursor_read_u32(reader->cursor, reader->base_offset + 12, + &reader->output_type_offset); + if (ret != CKB_SUCCESS) { + return ret; + } + + return CKB_SUCCESS; +} + +int cwhr_witness_args_reader_create(cwhr_witness_args_reader_t *reader, + cwhr_cursor_t *cursor) { + return cwhr_witness_args_reader_initialize(reader, cursor, 0, + cursor->total_length); +} + +int cwhr_witness_args_reader_has_lock(cwhr_witness_args_reader_t *reader) { + return reader->input_type_offset > reader->lock_offset; +} + +int cwhr_witness_args_reader_lock(cwhr_witness_args_reader_t *reader, + cwhr_bytes_reader_t *lock) { + return cwhr_bytes_reader_initialize( + lock, reader->cursor, reader->base_offset + (size_t)reader->lock_offset, + (size_t)(reader->input_type_offset - reader->lock_offset)); +} + +int cwhr_witness_args_reader_has_input_type( + cwhr_witness_args_reader_t *reader) { + return reader->output_type_offset > reader->input_type_offset; +} + +int cwhr_witness_args_reader_input_type(cwhr_witness_args_reader_t *reader, + cwhr_bytes_reader_t *input_type) { + return cwhr_bytes_reader_initialize( + input_type, reader->cursor, + reader->base_offset + (size_t)reader->input_type_offset, + (size_t)(reader->output_type_offset - reader->input_type_offset)); +} + +int cwhr_witness_args_reader_has_output_type( + cwhr_witness_args_reader_t *reader) { + return reader->length > (size_t)reader->output_type_offset; +} + +int cwhr_witness_args_reader_output_type(cwhr_witness_args_reader_t *reader, + cwhr_bytes_reader_t *output_type) { + return cwhr_bytes_reader_initialize( + output_type, reader->cursor, + reader->base_offset + (size_t)reader->output_type_offset, + reader->length - (size_t)reader->output_type_offset); +} + +int cwhr_witness_args_reader_verify(cwhr_witness_args_reader_t *reader, + int compatible) { + uint32_t size = 0xFFFFFFFF; + int ret = cwhr_cursor_read_u32(reader->cursor, reader->base_offset, &size); + if (ret != CKB_SUCCESS) { + return ret; + } + if (((size_t)size) != reader->length) { + CWHR_DEBUG("WitnessArgs has incorrect length! Expected: %u, actual: %ld\n", + size, reader->length); + return CWHR_ERROR_CODE; + } + + if (reader->lock_offset % 4 != 0) { + CWHR_DEBUG("Offset field is not aligned to 4 bytes!\n"); + return CWHR_ERROR_CODE; + } + uint32_t field_count = reader->lock_offset / 4 - 1; + if (field_count < 3) { + CWHR_DEBUG("WitnessArgs must have at least 3 fields!\n"); + return CWHR_ERROR_CODE; + } + if ((!compatible) && (field_count > 3)) { + CWHR_DEBUG( + "WitnessArgs has remaining field but compatible flag is turned off!\n"); + return CWHR_ERROR_CODE; + } + + uint32_t previous_offset = 0xFFFFFFFF; + ret = cwhr_cursor_read_u32(reader->cursor, reader->base_offset + 4, + &previous_offset); + if (ret != CKB_SUCCESS) { + return ret; + } + for (uint32_t i = 1; i < field_count; i++) { + uint32_t offset = 0xFFFFFFFF; + ret = cwhr_cursor_read_u32(reader->cursor, reader->base_offset + 4 + 4 * i, + &offset); + if (ret != CKB_SUCCESS) { + return ret; + } + if (previous_offset > offset) { + CWHR_DEBUG("Offset %u is bigger than offset %u in WitnessArgs!\n", i - 1, + i); + return CWHR_ERROR_CODE; + } + previous_offset = offset; + } + if (((size_t)previous_offset) > reader->length) { + CWHR_DEBUG("The last offset is bigger than total length!\n"); + return CWHR_ERROR_CODE; + } + + if (cwhr_witness_args_reader_has_lock(reader)) { + cwhr_bytes_reader_t lock; + ret = cwhr_witness_args_reader_lock(reader, &lock); + if (ret != CKB_SUCCESS) { + return ret; + } + ret = cwhr_bytes_reader_verify(&lock, compatible); + if (ret != CKB_SUCCESS) { + return ret; + } + } + if (cwhr_witness_args_reader_has_input_type(reader)) { + cwhr_bytes_reader_t input_type; + ret = cwhr_witness_args_reader_input_type(reader, &input_type); + if (ret != CKB_SUCCESS) { + return ret; + } + ret = cwhr_bytes_reader_verify(&input_type, compatible); + if (ret != CKB_SUCCESS) { + return ret; + } + } + if (cwhr_witness_args_reader_has_output_type(reader)) { + cwhr_bytes_reader_t output_type; + ret = cwhr_witness_args_reader_output_type(reader, &output_type); + if (ret != CKB_SUCCESS) { + return ret; + } + ret = cwhr_bytes_reader_verify(&output_type, compatible); + if (ret != CKB_SUCCESS) { + return ret; + } + } + + return CKB_SUCCESS; +} + +#endif /* CKB_WITNESS_ARGS_HANDWRITTEN_READER_ */ diff --git a/docs/c/dao.html b/docs/c/dao.html index 963e25f..63275d4 100644 --- a/docs/c/dao.html +++ b/docs/c/dao.html @@ -50,8 +50,9 @@

DAO

-

This file provides NervosDAO on chain script implementation. It is designed to -work as the type script of a cell. Please refer to Nervos DAO RFC +

This file provides NervosDAO on chain script implementation. It is designed +to work as the type script of a cell. Please refer to Nervos DAO +RFC on more details.

@@ -66,7 +67,8 @@

DAO

Necessary headers. This script will need to perform syscalls to read current -transaction structure, then parse WitnessArgs data structure in molecule format.

+transaction structure, then parse WitnessArgs data structure in molecule +format.

@@ -91,7 +93,6 @@

DAO

#define ERROR_SYSCALL -4 #define ERROR_BUFFER_NOT_ENOUGH -10 #define ERROR_ENCODING -11 -#define ERROR_WITNESS_TOO_LONG -12 #define ERROR_OVERFLOW -13 #define ERROR_INVALID_WITHDRAW_BLOCK -14 #define ERROR_INCORRECT_CAPACITY -15 @@ -120,7 +121,14 @@

DAO

#if ERROR_MARKER_EXHAUSTED == CKB_INDEX_OUT_OF_BOUND #error "Exhausted marker cannot be the same as CKB_INDEX_OUT_OF_BOUND!" -#endif +#endif + +#define CWHR_DEBUG(...) +#define CWHR_ERROR_CODE -22 +#include "witness_args_handwritten_reader.h" + +#define HASH_SIZE 32 +#define HEADER_SIZE 4096 @@ -131,17 +139,13 @@

DAO

-

Common definitions here, one important limitation, is that this script only works -with scripts and witnesses that are no larger than 32KB. We believe this should be enough -for most cases.

+

Note witness will be loaded via a reader that supports arbitrary length. The +32KB size here is merely the temporary buffer used to hold part of witness, it +does not necessary limit the total witness size to 32KB.

-
#define HASH_SIZE 32
-#define HEADER_SIZE 4096
-/* 32 KB */
-#define MAX_WITNESS_SIZE 32768
-#define SCRIPT_SIZE 32768
+
#define WITNESS_BUF_SIZE 32768
@@ -152,11 +156,13 @@

DAO

-

One lock period of NervosDAO is set as 180 epochs, which is roughly 30 days.

+

The only script that will be loaded by Nervos DAO contract, is Nervos DAO type +script itself. This type script has empty args, which means the type script will +never exceed 32K. We are safe keeping this value here.

-
#define LOCK_PERIOD_EPOCHS 180
+
#define SCRIPT_SIZE 32768
@@ -167,6 +173,21 @@

DAO

+

One lock period of NervosDAO is set as 180 epochs, which is roughly 30 days.

+ + + +
#define LOCK_PERIOD_EPOCHS 180
+ + + + +
  • +
    + +
    + +

    Common definitions to parse epoch value in block headers.

    @@ -184,11 +205,11 @@

    DAO

  • -
  • +
  • - +

    Fetches deposit header index. The index is kept in the witness of the same index as the input cell. The witness is first treated as a WitnessArgs object @@ -199,52 +220,40 @@

    DAO

    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];
    +  unsigned char witness[WITNESS_BUF_SIZE];
     
    -  len = MAX_WITNESS_SIZE;
    -  ret = ckb_load_witness(witness, &len, 0, input_index, CKB_SOURCE_INPUT);
    +  cwhr_cursor_t cursor;
    +  ret = cwhr_cursor_initialize(
    +      &cursor, cwhr_witness_loader_create(input_index, CKB_SOURCE_INPUT),
    +      witness, WITNESS_BUF_SIZE);
       if (ret != CKB_SUCCESS) {
    -    return ERROR_SYSCALL;
    +    return ret;
       }
    -  if (len > MAX_WITNESS_SIZE) {
    -    return ERROR_WITNESS_TOO_LONG;
    +  cwhr_witness_args_reader_t reader;
    +  ret = cwhr_witness_args_reader_create(&reader, &cursor);
    +  if (ret != CKB_SUCCESS) {
    +    return ret;
       }
    -
    -  mol_seg_t witness_seg;
    -  witness_seg.ptr = (uint8_t *)witness;
    -  witness_seg.size = len;
    -
    -  if (MolReader_WitnessArgs_verify(&witness_seg, false) != MOL_OK) {
    +  ret = cwhr_witness_args_reader_verify(&reader, 0);
    +  if (ret != CKB_SUCCESS) {
         return ERROR_ENCODING;
    -  }
    - -
  • - - -
  • -
    - -
    - -
    -

    Load input_type

    - -
    - -
      mol_seg_t type_seg = MolReader_WitnessArgs_get_input_type(&witness_seg);
    +  }
     
    -  if (MolReader_BytesOpt_is_none(&type_seg)) {
    +  if (!cwhr_witness_args_reader_has_input_type(&reader)) {
         return ERROR_ENCODING;
       }
     
    -  mol_seg_t type_bytes_seg = MolReader_Bytes_raw_bytes(&type_seg);
    -  if (type_bytes_seg.size != 8) {
    +  cwhr_bytes_reader_t input_type;
    +  ret = cwhr_witness_args_reader_input_type(&reader, &input_type);
    +  if (ret != CKB_SUCCESS) {
    +    return ret;
    +  }
    +
    +  if (cwhr_bytes_reader_length(&input_type) != 8) {
         return ERROR_ENCODING;
       }
     
    -  *index = *type_bytes_seg.ptr;
    -  return CKB_SUCCESS;
    +  return cwhr_bytes_reader_memcpy(&input_type, index);
     }
  • @@ -373,15 +382,17 @@

    DAO

    Validates an input cell is indeed deposited to NervosDAO in deposited_block_number, then calculates the capacity one can withdraw from -this deposited cell. The function will tries to first read an index value from -the witness of the position as provided input cell index. Then use the read -index value as an index into header_deps section of current transaction for -a header. The header is then used as withdraw header to calculate deposit period.

    +this deposited cell. The function will tries to first read an index value +from the witness of the position as provided input cell index. Then use the +read index value as an index into header_deps section of current +transaction for a header. The header is then used as withdraw header to +calculate deposit period.

    static int calculate_dao_input_capacity(size_t input_index,
                                             uint64_t deposited_block_number,
    +                                        uint64_t deposited_occupied_capacity,
                                             uint64_t original_capacity,
                                             uint64_t *calculated_capacity) {
       uint64_t len = 0;
    @@ -489,7 +500,7 @@ 

    DAO

    deposited_epochs++; } uint64_t lock_epochs = (deposited_epochs + (LOCK_PERIOD_EPOCHS - 1)) / - LOCK_PERIOD_EPOCHS * LOCK_PERIOD_EPOCHS;
    + LOCK_PERIOD_EPOCHS * LOCK_PERIOD_EPOCHS; @@ -522,8 +533,7 @@

    DAO

    -
      uint64_t minimal_since_epoch_number =
    -      deposit_data.epoch_number + lock_epochs;
    +            
      uint64_t minimal_since_epoch_number = deposit_data.epoch_number + lock_epochs;
       uint64_t minimal_since_epoch_index = deposit_data.epoch_index;
       uint64_t minimal_since_epoch_length = deposit_data.epoch_length;
    @@ -536,7 +546,8 @@

    DAO

    -

    Loads since value from current input to make sure correct lock period is set.

    +

    Loads since value from current input to make sure correct lock period is +set.

    @@ -610,8 +621,8 @@

    DAO

    -

    Now we can calculate the maximum amount one can withdraw from this cell. Please -refer to Nervos DAO RFC for more details on the formula used here.

    +

    Now we can calculate the maximum amount one can withdraw from this cell. +Please refer to Nervos DAO RFC for more details on the formula used here.

    @@ -627,38 +638,12 @@

    DAO

    -

    Nervos DAO interest is only calculated on occupied capacity, which means all -capacities that are not used as storage cost in a cell.

    - - - -
      uint64_t occupied_capacity = 0;
    -  len = 8;
    -  ret = ckb_load_cell_by_field(((unsigned char *)&occupied_capacity), &len, 0,
    -                               input_index, CKB_SOURCE_INPUT,
    -                               CKB_CELL_FIELD_OCCUPIED_CAPACITY);
    -  if (ret != CKB_SUCCESS) {
    -    return ERROR_SYSCALL;
    -  }
    -  if (len != 8) {
    -    return ERROR_SYSCALL;
    -  }
    - - - - -
  • -
    - -
    - -

    Like any serious smart contracts, we will perform overflow checks here.

      uint64_t counted_capacity = 0;
    -  if (__builtin_usubl_overflow(original_capacity, occupied_capacity,
    +  if (__builtin_usubl_overflow(original_capacity, deposited_occupied_capacity,
                                    &counted_capacity)) {
         return ERROR_OVERFLOW;
       }
    @@ -668,7 +653,7 @@ 

    DAO

    ((__int128)deposit_accumulate_rate); uint64_t withdraw_capacity = 0; - if (__builtin_uaddl_overflow(occupied_capacity, + if (__builtin_uaddl_overflow(deposited_occupied_capacity, (uint64_t)withdraw_counted_capacity, &withdraw_capacity)) { return ERROR_OVERFLOW; @@ -681,11 +666,11 @@

    DAO

  • -
  • +
  • - +

    In the phase 1 of NervosDAO script, we will consume a deposited cell, and create a withdrawing cell. The withdrawing cell must be put in the same index @@ -712,11 +697,11 @@

    DAO

  • -
  • +
  • - +

    Check type script

    @@ -724,7 +709,7 @@

    DAO

      len = HASH_SIZE;
       int ret = ckb_load_cell_by_field(hash1, &len, 0, index, CKB_SOURCE_OUTPUT,
    -                               CKB_CELL_FIELD_TYPE_HASH);
    +                                   CKB_CELL_FIELD_TYPE_HASH);
       if (ret != CKB_SUCCESS) {
         return ret;
       }
    @@ -738,11 +723,11 @@ 

    DAO

  • -
  • +
  • - +

    Check capacity

    @@ -766,11 +751,11 @@

    DAO

  • -
  • +
  • - +

    Check cell data

    @@ -781,30 +766,63 @@

    DAO

    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); + uint8_t output_data[16]; + len = 16; + ret = ckb_load_cell_data(output_data, &len, 0, index, CKB_SOURCE_OUTPUT); if (ret != CKB_SUCCESS) { return ret; } - if (len != 8) { + if (len != 8 && len != 16) { return ERROR_SYSCALL; } + uint64_t stored_block_number = *((uint64_t *)output_data); if (stored_block_number != deposit_header.block_number) { return ERROR_INVALID_WITHDRAWING_CELL; } + if (len == 16) { + uint64_t stored_occupied_capacity = *((uint64_t *)(&output_data[8])); + uint64_t actual_occupied_capacity = 0; + len = 8; + ret = ckb_load_cell_by_field((unsigned char *)&actual_occupied_capacity, + &len, 0, index, CKB_SOURCE_INPUT, + CKB_CELL_FIELD_OCCUPIED_CAPACITY); + if (ret != CKB_SUCCESS) { + return ret; + } + if (len != 8) { + return ERROR_SYSCALL; + } + if (stored_occupied_capacity != actual_occupied_capacity) { + return ERROR_INVALID_WITHDRAWING_CELL; + } + } else { + uint64_t input_lock_length = 0; + ret = ckb_load_cell_by_field(NULL, &input_lock_length, 0, index, + CKB_SOURCE_INPUT, CKB_CELL_FIELD_LOCK); + if (ret != CKB_SUCCESS) { + return ret; + } + uint64_t output_lock_length = 0; + ret = ckb_load_cell_by_field(NULL, &output_lock_length, 0, index, + CKB_SOURCE_OUTPUT, CKB_CELL_FIELD_LOCK); + if (ret != CKB_SUCCESS) { + return ret; + } + if (input_lock_length != output_lock_length) { + return ERROR_INVALID_WITHDRAWING_CELL; + } + } return CKB_SUCCESS; } -static int validate_input(size_t index, uint64_t *input_capacities, +static int validate_input(size_t index, uint64_t *input_capacities, uint64_t *output_withdrawing, unsigned char *script_hash) { int dao_input = 0; uint64_t capacity = 0; uint64_t len = 8; int ret = ckb_load_cell_by_field(((unsigned char *)&capacity), &len, 0, index, - CKB_SOURCE_INPUT, CKB_CELL_FIELD_CAPACITY); + CKB_SOURCE_INPUT, CKB_CELL_FIELD_CAPACITY); if (ret == CKB_INDEX_OUT_OF_BOUND) { return ERROR_MARKER_EXHAUSTED; } else if (ret == CKB_SUCCESS) { @@ -814,16 +832,16 @@

    DAO

    unsigned char current_script_hash[HASH_SIZE]; len = HASH_SIZE; ret = ckb_load_cell_by_field(current_script_hash, &len, 0, index, - CKB_SOURCE_INPUT, CKB_CELL_FIELD_TYPE_HASH);
    + CKB_SOURCE_INPUT, CKB_CELL_FIELD_TYPE_HASH);
  • -
  • +
  • - +

    When an input cell has the same type script hash as current running we know we are dealing with a script using NervosDAO script.

    @@ -843,30 +861,39 @@

    DAO

  • -
  • +
  • - +

    Normal input, use its own capacity

        if (__builtin_uaddl_overflow(*input_capacities, capacity,
    -                                  input_capacities)) {
    +                                 input_capacities)) {
           return ERROR_OVERFLOW;
         }
    -  } else {
    + } else { + uint8_t data[16]; + len = 16; + ret = ckb_load_cell_data(data, &len, 0, index, CKB_SOURCE_INPUT); + if (ret != CKB_SUCCESS) { + return ret; + } + if (len != 8 && len != 16) { + return ERROR_SYSCALL; + }
  • -
  • +
  • - +

    In a Nervos DAO transaction, we might have 2 types of input cells using Nervos DAO type script:

    @@ -874,36 +901,43 @@

    DAO

  • A deposited cell
  • A withdrawing cell
  • -

    If you are also looking at the Nervos DAO RFC, a deposited cell is created in -the initial deposit phase, and spent in withdraw phase 1; a withdrawing cell -is created in withdraw phase 1, then spent in withdraw phase 2.

    -

    The way to tell them apart, is that a deposited cell always contains 8 bytes -of 0 as cell data, while a withdrawing cell would contain a positive number -denoting the original deposited block number.

    +

    If you are also looking at the Nervos DAO RFC, a deposited cell is +created in the initial deposit phase, and spent in withdraw phase 1; a +withdrawing cell is created in withdraw phase 1, then spent in withdraw +phase 2.

    +

    The way to tell them apart, is that a deposited cell always contains 8 +bytes of 0 as cell data, while a withdrawing cell would contain a +positive number denoting the original deposited block number.

    -
        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 (block_number > 0) {
    +
        uint64_t block_number = *((uint64_t *)data);
    +
    +    if (block_number > 0) {
    +      uint64_t occupied_capacity = 0xFFFFFFFFFFFFFFFF;
    +      if (len == 16) {
    +        occupied_capacity = *((uint64_t *)(&data[8]));
    +      } else {
    +        len = 8;
    +        ret = ckb_load_cell_by_field(((unsigned char *)&occupied_capacity),
    +                                     &len, 0, index, CKB_SOURCE_INPUT,
    +                                     CKB_CELL_FIELD_OCCUPIED_CAPACITY);
    +        if (ret != CKB_SUCCESS) {
    +          return ERROR_SYSCALL;
    +        }
    +        if (len != 8) {
    +          return ERROR_SYSCALL;
    +        }
    +      }
    -
  • +
  • - +

    For a withdrawing cell, we can start calculate the maximum capacity that one can withdraw from it.

    @@ -911,8 +945,8 @@

    DAO

          uint64_t dao_capacity = 0;
    -      ret = calculate_dao_input_capacity(index, block_number, capacity,
    -                                          &dao_capacity);
    +      ret = calculate_dao_input_capacity(index, block_number, occupied_capacity,
    +                                         capacity, &dao_capacity);
           if (ret != CKB_SUCCESS) {
             return ret;
           }
    @@ -920,30 +954,33 @@

    DAO

  • -
  • +
  • - +

    Like any serious smart contracts, we will perform overflow checks here.

          if (__builtin_uaddl_overflow(*input_capacities, dao_capacity,
    -                                    input_capacities)) {
    +                                   input_capacities)) {
             return ERROR_OVERFLOW;
           }
    -    } else {
    + } else { + if (len != 8) { + return ERROR_SYSCALL; + }
  • -
  • +
  • - +

    For a deposited cell, we only need to check that a withdrawing cell for current one is generated. For simplicity, we are limiting the code so @@ -961,11 +998,11 @@

    DAO

  • -
  • +
  • - +

    Note that validate_withdrawing_cell above already verifies that an output cell for the current input cell at the same location exists.

    @@ -977,18 +1014,18 @@

    DAO

  • -
  • +
  • - +

    Like any serious smart contracts, we will perform overflow checks here.

          if (__builtin_uaddl_overflow(*input_capacities, capacity,
    -                                    input_capacities)) {
    +                                   input_capacities)) {
             return ERROR_OVERFLOW;
           }
         }
    @@ -997,13 +1034,13 @@ 

    DAO

    return 0; } -static int validate_output(size_t index, uint64_t *output_capacities, - uint64_t output_withdrawing, - unsigned char *script_hash) { +static int validate_output(size_t index, uint64_t *output_capacities, + uint64_t output_withdrawing, + unsigned char *script_hash) { uint64_t capacity = 0; uint64_t len = 8; int ret = ckb_load_cell_by_field(((unsigned char *)&capacity), &len, 0, index, - CKB_SOURCE_OUTPUT, CKB_CELL_FIELD_CAPACITY); + CKB_SOURCE_OUTPUT, CKB_CELL_FIELD_CAPACITY); if (ret == CKB_INDEX_OUT_OF_BOUND) { return ERROR_MARKER_EXHAUSTED; } @@ -1017,36 +1054,36 @@

    DAO

  • -
  • +
  • - +

    Like any serious smart contracts, we will perform overflow checks here.

      if (__builtin_uaddl_overflow(*output_capacities, capacity,
    -                                output_capacities)) {
    +                               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);
    +                               CKB_SOURCE_OUTPUT, CKB_CELL_FIELD_TYPE_HASH);
       if ((ret == CKB_SUCCESS) && len == HASH_SIZE &&
           (memcmp(script_hash, current_script_hash, HASH_SIZE) == 0)) {
  • -
  • +
  • - +

    Similarly to input, we also need to check if we are creating a deposited cell, or a withdrawing cell here. This can be easily determined @@ -1064,7 +1101,7 @@

    DAO

    uint64_t block_number = 0; len = 8; ret = ckb_load_cell_data((unsigned char *)&block_number, &len, 0, index, - CKB_SOURCE_OUTPUT); + CKB_SOURCE_OUTPUT); if (ret != CKB_SUCCESS) { return ret; } @@ -1079,7 +1116,6 @@

    DAO

    return 0; } - int main() { int ret; unsigned char script_hash[HASH_SIZE]; @@ -1092,15 +1128,15 @@

    DAO

  • -
  • +
  • - +
    -

    NervosDAO script requires script args part to be empty, this way we can ensure -that all DAO related scripts in a transaction is mapped to the same group, and -processed together in one execution.

    +

    NervosDAO script requires script args part to be empty, this way we can +ensure that all DAO related scripts in a transaction is mapped to the same +group, and processed together in one execution.

    @@ -1126,11 +1162,11 @@

    DAO

  • -
  • +
  • - +

    Load current script hash. Unlike a lock script which only cares for cells using its own lock script. The NervosDAO script here will need to loop @@ -1152,11 +1188,11 @@

    DAO

  • -
  • +
  • - +

    First, we will need to loop against all input cells in current transaction. For a normal transaction, we will just add up its own capacity. For a @@ -1170,11 +1206,11 @@

    DAO

  • -
  • +
  • - +

    Also let’s loop through all output cells, and calculate the sum of output capacities here.

    @@ -1194,33 +1230,35 @@

    DAO

  • -
  • +
  • - +
    -

    Reset the flag to ensure that it does not retain the value from the last iteration when -inputs have been exhausted.

    +

    Reset the flag to ensure that it does not retain the value from the last +iteration when inputs have been exhausted.

        output_withdrawing = 0;
     
         if (!input_exhausted) {
    -      ret = validate_input(index, &input_capacities, &output_withdrawing, script_hash);
    +      ret = validate_input(index, &input_capacities, &output_withdrawing,
    +                           script_hash);
           if (ret == ERROR_MARKER_EXHAUSTED) {
             input_exhausted = 1;
    -      } else if (ret != CKB_SUCCESS ){
    +      } else if (ret != CKB_SUCCESS) {
             return ret;
           }
         }
     
         if (!output_exhausted) {
    -      ret = validate_output(index, &output_capacities, output_withdrawing, script_hash);
    +      ret = validate_output(index, &output_capacities, output_withdrawing,
    +                            script_hash);
           if (ret == ERROR_MARKER_EXHAUSTED) {
             output_exhausted = 1;
    -      } else if (ret != CKB_SUCCESS ){
    +      } else if (ret != CKB_SUCCESS) {
             return ret;
           }
         }
    @@ -1231,15 +1269,15 @@ 

    DAO

  • -
  • +
  • - +
    -

    The final thing we need to check here, is that the sum of capacities in output -cells, cannot exceed the sum of capacities in all input cells with Nervos DAO -issuance considered.

    +

    The final thing we need to check here, is that the sum of capacities in +output cells, cannot exceed the sum of capacities in all input cells with +Nervos DAO issuance considered.

    diff --git a/src/tests/dao.rs b/src/tests/dao.rs index efaf26d..e1978ec 100644 --- a/src/tests/dao.rs +++ b/src/tests/dao.rs @@ -3,7 +3,7 @@ use byteorder::{ByteOrder, LittleEndian}; use ckb_crypto::secp::{Generator, Privkey}; use ckb_dao_utils::pack_dao_data; use ckb_error::assert_error_eq; -use ckb_script::{ScriptError, TransactionScriptsVerifier}; +use ckb_script::{ScriptError, ScriptGroupType, TransactionScriptsVerifier}; use ckb_types::{ bytes::Bytes, core::{ @@ -15,6 +15,7 @@ use ckb_types::{ packed::{Byte32, CellDep, CellInput, CellOutput, OutPoint, Script, WitnessArgs}, prelude::*, }; +use proptest::prelude::*; use rand::{thread_rng, Rng}; use std::sync::Arc; @@ -278,6 +279,170 @@ fn test_dao_single_cell() { verify_result.expect("pass verification"); } +#[test] +fn test_dao_single_cell_long_witness() { + 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, + ); + let type_script_hash = cell.type_().to_opt().unwrap().calc_script_hash(); + + 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); + data_loader + .epoches + .insert(withdraw_header.hash(), withdraw_epoch); + + let mut b = vec![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 mut b = vec![0; 8]; + LittleEndian::write_u64(&mut b, 1); + let witness = WitnessArgs::new_builder() + .input_type(Some(Bytes::from(b)).pack()) + .output_type(Some(Bytes::from(vec![1; 200 * 1024])).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 = Arc::new(ResolvedTransaction { + transaction: tx, + resolved_inputs, + resolved_cell_deps, + resolved_dep_groups: vec![], + }); + + // Note that the lock scripts included in this repo all suffer from 32K witness limit + // for now, which means verifying the full transaction here would fail. So we only verify + // the Nervos DAO script here. + let verify_result = TransactionScriptsVerifier::new(rtx, data_loader).verify_single( + ScriptGroupType::Type, + &type_script_hash, + MAX_CYCLES, + ); + verify_result.expect("pass verification"); +} + +proptest! { + #[test] + fn test_dao_single_cell_long_invalid_witness( + flip_bit in 0..64usize, + ) { + let mut data_loader = DummyDataLoader::new(); + + 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), + Bytes::new(), + ); + let type_script_hash = cell.type_().to_opt().unwrap().calc_script_hash(); + + 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); + data_loader + .epoches + .insert(withdraw_header.hash(), withdraw_epoch); + + let mut b = vec![0; 8]; + LittleEndian::write_u64(&mut b, 1554); + let input_cell_meta = CellMetaBuilder::from_cell_output(cell.clone(), 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 mut b = vec![0; 8]; + LittleEndian::write_u64(&mut b, 1); + let witness = WitnessArgs::new_builder() + .input_type(Some(Bytes::from(b)).pack()) + .output_type(Some(Bytes::from(vec![1; 200 * 1024])).pack()) + .build(); + let mut witness_bytes = witness.as_bytes().to_vec(); + // Flip one bit in the first 16 bytes to make the witness invalid + witness_bytes[flip_bit / 8] ^= 1 << (flip_bit % 8); + 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(Bytes::from(witness_bytes).pack()); + let (tx, mut resolved_cell_deps2) = complete_tx(&mut data_loader, builder); + for dep in resolved_cell_deps2.drain(..) { + resolved_cell_deps.push(dep); + } + let rtx = Arc::new(ResolvedTransaction { + transaction: tx, + resolved_inputs, + resolved_cell_deps, + resolved_dep_groups: vec![], + }); + + // Note that the lock scripts included in this repo all suffer from 32K witness limit + // for now, which means verifying the full transaction here would fail. So we only verify + // the Nervos DAO script here. + let verify_result = TransactionScriptsVerifier::new(rtx, data_loader).verify_single( + ScriptGroupType::Type, + &type_script_hash, + MAX_CYCLES, + ); + assert_eq!( + verify_result.unwrap_err(), + ScriptError::validation_failure(&cell.type_().to_opt().unwrap(), -11), + ); + } +} + #[test] fn test_dao_single_cell_epoch_edge() { let mut data_loader = DummyDataLoader::new(); @@ -1830,3 +1995,510 @@ fn test_dao_all_dao_actions() { let verify_result = TransactionScriptsVerifier::new(rtx, data_loader).verify(MAX_CYCLES); verify_result.expect("pass verification"); } + +#[test] +fn test_dao_create_withdrawing_cell_detailed_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(), + ); + let occupied_capacity = cell.occupied_capacity(Capacity::bytes(8).unwrap()).unwrap(); + + data_loader + .headers + .insert(deposit_header.hash(), deposit_header.clone()); + data_loader + .epoches + .insert(deposit_header.hash(), deposit_epoch); + + 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 = vec![0; 16]; + LittleEndian::write_u64(&mut b[0..8], 1554); + LittleEndian::write_u64(&mut b[8..16], occupied_capacity.as_u64()); + 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 = Arc::new(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_invalid_detailed_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(), + ); + let occupied_capacity = cell.occupied_capacity(Capacity::bytes(8).unwrap()).unwrap(); + + data_loader + .headers + .insert(deposit_header.hash(), deposit_header.clone()); + data_loader + .epoches + .insert(deposit_header.hash(), deposit_epoch); + + let input_cell_meta = CellMetaBuilder::from_cell_output(cell.clone(), 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 = vec![0; 16]; + LittleEndian::write_u64(&mut b[0..8], 1554); + LittleEndian::write_u64(&mut b[8..16], occupied_capacity.as_u64() - 10); + 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 = Arc::new(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::validation_failure(&cell.type_().to_opt().unwrap(), -20).input_type_script(0), + ); +} + +#[test] +fn test_dao_create_withdrawing_cell_invalid_detailed_data_size() { + 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(), + ); + let occupied_capacity = cell.occupied_capacity(Capacity::bytes(8).unwrap()).unwrap(); + + data_loader + .headers + .insert(deposit_header.hash(), deposit_header.clone()); + data_loader + .epoches + .insert(deposit_header.hash(), deposit_epoch); + + let input_cell_meta = CellMetaBuilder::from_cell_output(cell.clone(), 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 = vec![0; 20]; + LittleEndian::write_u64(&mut b[0..8], 1554); + LittleEndian::write_u64(&mut b[8..16], occupied_capacity.as_u64()); + 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 = Arc::new(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::validation_failure(&cell.type_().to_opt().unwrap(), -4).input_type_script(0), + ); +} + +#[test] +fn test_dao_create_withdrawing_cell_smaller_lock() { + 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, + ); + + data_loader + .headers + .insert(deposit_header.hash(), deposit_header.clone()); + data_loader + .epoches + .insert(deposit_header.hash(), deposit_epoch); + + let input_cell_meta = CellMetaBuilder::from_cell_output(cell.clone(), 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), + Bytes::new(), + ); + + let mut b = vec![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 = Arc::new(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::validation_failure(&cell.type_().to_opt().unwrap(), -20).input_type_script(0), + ); +} + +#[test] +fn test_dao_single_cell_detailed_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 (withdraw_header, withdraw_epoch) = gen_header(2000610, 10001000, 575, 2000000, 1100); + let (cell, previous_out_point) = gen_dao_cell( + &mut data_loader, + Capacity::shannons(124256780000), + lock_args, + ); + let occupied_capacity = cell + .occupied_capacity(Capacity::bytes(16).unwrap()) + .unwrap(); + + 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); + data_loader + .epoches + .insert(withdraw_header.hash(), withdraw_epoch); + + let mut b = vec![0; 16]; + LittleEndian::write_u64(&mut b, 1554); + LittleEndian::write_u64(&mut b[8..16], occupied_capacity.as_u64()); + 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 mut b = vec![0; 8]; + LittleEndian::write_u64(&mut b, 1); + let witness = WitnessArgs::new_builder() + .input_type(Some(Bytes::from(b)).pack()) + .build(); + let builder = TransactionBuilder::default() + .input(CellInput::new(previous_out_point, 0x2003e8022a0002f3)) + .output(cell_output_with_only_capacity(124268105678)) + .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 = Arc::new(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_single_cell_detailed_data_different_occupied_capacity() { + let mut data_loader = DummyDataLoader::new(); + let (privkey, _) = 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(124256780000), + Bytes::new(), + ); + let occupied_capacity = cell + .occupied_capacity(Capacity::bytes(16).unwrap()) + .unwrap() + .safe_add(Capacity::bytes(20).unwrap()) + .unwrap(); + let type_script_hash = cell.type_().to_opt().unwrap().calc_script_hash(); + + 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); + data_loader + .epoches + .insert(withdraw_header.hash(), withdraw_epoch); + + let mut b = vec![0; 16]; + LittleEndian::write_u64(&mut b, 1554); + LittleEndian::write_u64(&mut b[8..16], occupied_capacity.as_u64()); + 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 mut b = vec![0; 8]; + LittleEndian::write_u64(&mut b, 1); + let witness = WitnessArgs::new_builder() + .input_type(Some(Bytes::from(b)).pack()) + .build(); + let builder = TransactionBuilder::default() + .input(CellInput::new(previous_out_point, 0x2003e8022a0002f3)) + .output(cell_output_with_only_capacity(124268105678)) + .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 = Arc::new(ResolvedTransaction { + transaction: tx, + resolved_inputs, + resolved_cell_deps, + resolved_dep_groups: vec![], + }); + + let verify_result = TransactionScriptsVerifier::new(rtx, data_loader).verify_single( + ScriptGroupType::Type, + &type_script_hash, + MAX_CYCLES, + ); + verify_result.expect("pass verification"); +} + +#[test] +fn test_dao_single_cell_detailed_data_invalid_withdraw_amount() { + 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(124256780000), + lock_args, + ); + let occupied_capacity = cell + .occupied_capacity(Capacity::bytes(16).unwrap()) + .unwrap(); + let type_script_hash = cell.type_().to_opt().unwrap().calc_script_hash(); + + 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); + data_loader + .epoches + .insert(withdraw_header.hash(), withdraw_epoch); + + let mut b = vec![0; 16]; + LittleEndian::write_u64(&mut b, 1554); + LittleEndian::write_u64( + &mut b[8..16], + occupied_capacity + .safe_add(Capacity::shannons(1)) + .unwrap() + .as_u64(), + ); + let input_cell_meta = CellMetaBuilder::from_cell_output(cell.clone(), 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 mut b = vec![0; 8]; + LittleEndian::write_u64(&mut b, 1); + let witness = WitnessArgs::new_builder() + .input_type(Some(Bytes::from(b)).pack()) + .build(); + let builder = TransactionBuilder::default() + .input(CellInput::new(previous_out_point, 0x2003e8022a0002f3)) + .output(cell_output_with_only_capacity(124268105678)) + .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 = Arc::new(ResolvedTransaction { + transaction: tx, + resolved_inputs, + resolved_cell_deps, + resolved_dep_groups: vec![], + }); + + let verify_result = TransactionScriptsVerifier::new(rtx, data_loader).verify_single( + ScriptGroupType::Type, + &type_script_hash, + MAX_CYCLES, + ); + assert_eq!( + verify_result.unwrap_err(), + ScriptError::validation_failure(&cell.type_().to_opt().unwrap(), -15), + ); +}