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
-
-#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),
+ );
+}