diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index fed0748e..0c25486d 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -2,7 +2,7 @@ name: CI workflow on: pull_request: - types: [ opened, synchronize, reopened ] + types: [opened, synchronize, reopened] push: branches: - "develop" @@ -18,37 +18,37 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ ubuntu-latest, macos-latest, windows-2019 ] + os: [ubuntu-latest, macos-latest, windows-2019] steps: - - uses: actions/checkout@v2 - - if: matrix.os == 'windows-2019' - name: Windows Dependencies - run: | - iwr -useb get.scoop.sh -outfile 'install-scoop.ps1' - .\install-scoop.ps1 -RunAsAdmin - echo "LIBCLANG_PATH=$($HOME)/scoop/apps/llvm/current/bin" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append - echo "$env:USERPROFILE\scoop\shims" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append - scoop install llvm yasm - - name: UnitTest - run: make test + - uses: actions/checkout@v2 + - if: matrix.os == 'windows-2019' + name: Windows Dependencies + run: | + iwr -useb get.scoop.sh -outfile 'install-scoop.ps1' + .\install-scoop.ps1 -RunAsAdmin + echo "LIBCLANG_PATH=$($HOME)/scoop/apps/llvm/current/bin" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + echo "$env:USERPROFILE\scoop\shims" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + scoop install llvm yasm + - name: UnitTest + run: make test build-examples: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ ubuntu-latest, macos-latest, windows-2019 ] + os: [ubuntu-latest, macos-latest, windows-2019] steps: - - uses: actions/checkout@v2 - - if: matrix.os == 'windows-2019' - name: Windows Dependencies - run: | - iwr -useb get.scoop.sh -outfile 'install-scoop.ps1' - .\install-scoop.ps1 -RunAsAdmin - echo "LIBCLANG_PATH=$($HOME)/scoop/apps/llvm/current/bin" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append - echo "$env:USERPROFILE\scoop\shims" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append - scoop install llvm yasm - - name: Build Cargo Examples - run: cargo build --examples + - uses: actions/checkout@v2 + - if: matrix.os == 'windows-2019' + name: Windows Dependencies + run: | + iwr -useb get.scoop.sh -outfile 'install-scoop.ps1' + .\install-scoop.ps1 -RunAsAdmin + echo "LIBCLANG_PATH=$($HOME)/scoop/apps/llvm/current/bin" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + echo "$env:USERPROFILE\scoop\shims" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + scoop install llvm yasm + - name: Build Cargo Examples + run: cargo build --examples linters: runs-on: ${{ matrix.os }} @@ -56,25 +56,26 @@ jobs: matrix: os: [ubuntu-latest, macos-latest] steps: - - uses: actions/checkout@v2 - - name: Linters - run: | - cargo fmt --version || rustup component add rustfmt - cargo clippy --version || rustup component add clippy - make fmt - make clippy + - uses: actions/checkout@v2 + - name: Linters + run: | + cargo fmt --version || rustup component add rustfmt + cargo clippy --version || rustup component add clippy + make fmt + make clippy security-audit: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - name: Security Audit & Licenses - run: | - rustup toolchain install stable --profile minimal - cargo deny --version || cargo install cargo-deny --locked - make security-audit - make check-crates - make check-licenses + - uses: actions/checkout@v2 + - name: Security Audit & Licenses + run: | + rustup toolchain install stable --profile minimal + rm rust-toolchain.toml + cargo deny --version || cargo install cargo-deny --locked + make security-audit + make check-crates + make check-licenses ci-success: name: ci diff --git a/Cargo.toml b/Cargo.toml index 77114fdc..43360cc5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,12 +1,18 @@ [package] name = "ckb-sdk" version = "3.5.0" -authors = [ "Linfeng Qian ", "Nervos Core Dev " ] +authors = [ + "Linfeng Qian ", + "Nervos Core Dev ", +] + + edition = "2018" license = "MIT" description = "Rust SDK for CKB" homepage = "https://github.com/nervosnetwork/ckb-sdk-rust" repository = "https://github.com/nervosnetwork/ckb-sdk-rust" +exclude = ["/test-data"] [dependencies] serde = { version = "1.0", features = ["derive"] } @@ -17,7 +23,10 @@ anyhow = "1.0.63" bech32 = "0.8.1" derive-getters = "0.2.1" log = "0.4.6" -reqwest = { version = "0.11", default-features = false, features = [ "json", "blocking" ] } +reqwest = { version = "0.11", default-features = false, features = [ + "json", + "blocking", +] } secp256k1 = { version = "0.29.0", features = ["recovery"] } tokio-util = { version = "0.7.7", features = ["codec"] } tokio = { version = "1" } @@ -28,6 +37,10 @@ parking_lot = "0.12" lru = "0.7.1" dashmap = "5.4" dyn-clone = "1.0" +faster-hex = "0.9.0" +ripemd = "0.1.3" +sha2 = "0.10.6" +ed25519-dalek = "2.1.1" ckb-types = "0.119.0" ckb-dao-utils = "0.119.0" @@ -42,7 +55,7 @@ sha3 = "0.10.1" enum-repr-derive = "0.2.0" # for feature test -rand = { version = "0.7.3", optional = true } +rand = { version = "0.7.3" } ckb-mock-tx-types = { version = "0.119.0" } ckb-chain-spec = "0.119.0" @@ -57,7 +70,10 @@ rustls-tls = ["reqwest/rustls-tls"] test = [] [dev-dependencies] -clap = { version = "=4.4.18", features = [ "derive" ] } # TODO clap v4.5 requires rustc v1.74.0+ +clap = { version = "4.4.18", features = ["derive"] } httpmock = "0.6" async-global-executor = "2.3.1" -hex = "0.4" + + +[target.'cfg(unix)'.dev-dependencies] +openssl = "0.10" diff --git a/deny.toml b/deny.toml index df53c8cc..314be59f 100644 --- a/deny.toml +++ b/deny.toml @@ -74,7 +74,8 @@ ignore = [ #{ id = "RUSTSEC-0000-0000", reason = "you can specify a reason the advisory is ignored" }, #"a-crate-that-is-yanked@0.1.1", # you can also ignore yanked crate versions if you wish #{ crate = "a-crate-that-is-yanked@0.1.1", reason = "you can specify why you are ignoring the yanked crate" - "RUSTSEC-2024-0370" # proc-macro-error's maintainer seems to be unreachable, ignore this + "RUSTSEC-2024-0370", # proc-macro-error's maintainer seems to be unreachable, ignore this + "RUSTSEC-2024-0384", # instant is no longer maintained, ignore this ] # If this is true, then cargo deny will use the git executable to fetch advisory database. # If this is false, then it uses a built-in git library. @@ -97,8 +98,8 @@ allow = [ "ISC", "MIT", "Unicode-DFS-2016", - "BSL-1.0", # xxhash-rust 0.8.10 - + "BSL-1.0", # xxhash-rust 0.8.10 + "Unicode-3.0", #"MIT", #"Apache-2.0", #"Apache-2.0 WITH LLVM-exception", diff --git a/examples/omnilock_examples.rs b/examples/omnilock_examples.rs new file mode 100644 index 00000000..15cad958 --- /dev/null +++ b/examples/omnilock_examples.rs @@ -0,0 +1,68 @@ +use ckb_sdk::{ + constants::ONE_CKB, + transaction::{ + builder::{CkbTransactionBuilder, SimpleTransactionBuilder}, + handler::{omnilock, HandlerContexts}, + input::InputIterator, + signer::{SignContexts, TransactionSigner}, + TransactionBuilderConfiguration, + }, + unlock::OmniLockConfig, + Address, CkbRpcClient, NetworkInfo, +}; +use ckb_types::{ + h256, + packed::CellOutput, + prelude::{Builder, Entity, Pack}, +}; +use std::{error::Error as StdErr, str::FromStr}; + +fn main() -> Result<(), Box> { + let network_info = NetworkInfo::testnet(); + let sender = Address::from_str("ckt1qrejnmlar3r452tcg57gvq8patctcgy8acync0hxfnyka35ywafvkqgqgpy7m88v3gxnn3apazvlpkkt32xz3tg5qq3kzjf3")?; + let receiver = Address::from_str("ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqv5dsed9par23x4g58seaw58j3ym5ml2hs8ztche")?; + + let configuration = TransactionBuilderConfiguration::new_with_network(network_info.clone())?; + + let iterator = InputIterator::new_with_address(&[sender.clone()], configuration.network_info()); + let mut builder = SimpleTransactionBuilder::new(configuration, iterator); + + let output = CellOutput::new_builder() + .capacity((128 * ONE_CKB).pack()) + .lock((&receiver).into()) + .build(); + builder.add_output_and_data(output.clone(), ckb_types::packed::Bytes::default()); + builder.set_change_lock((&sender).into()); + + let omni_cfg = OmniLockConfig::from_addr(&sender).unwrap(); + let context = omnilock::OmnilockScriptContext::new(omni_cfg.clone(), network_info.url.clone()); + + let mut contexts = HandlerContexts::default(); + contexts.add_context(Box::new(context) as Box<_>); + + let mut tx_with_groups = builder.build(&contexts)?; + + let json_tx = ckb_jsonrpc_types::TransactionView::from(tx_with_groups.get_tx_view().clone()); + println!("tx: {}", serde_json::to_string_pretty(&json_tx).unwrap()); + + let private_key = h256!("0x6c9ed03816e3111e49384b8d180174ad08e29feb1393ea1b51cef1c505d4e36a"); + TransactionSigner::new(&network_info).sign_transaction( + &mut tx_with_groups, + &SignContexts::new_omnilock( + [secp256k1::SecretKey::from_slice(private_key.as_bytes())?].to_vec(), + omni_cfg, + ckb_sdk::unlock::OmniUnlockMode::Normal, + ), + )?; + + let json_tx = ckb_jsonrpc_types::TransactionView::from(tx_with_groups.get_tx_view().clone()); + println!("tx: {}", serde_json::to_string_pretty(&json_tx).unwrap()); + + let tx_hash = CkbRpcClient::new(network_info.url.as_str()) + .send_transaction(json_tx.inner, None) + .expect("send transaction"); + // example tx: 0xc0c9954a3299b540e63351146a301438372abf93682d96c7cce691c334dd5757 + println!(">>> tx {} sent! <<<", tx_hash); + + Ok(()) +} diff --git a/examples/omnilock_multisig_example.rs b/examples/omnilock_multisig_example.rs new file mode 100644 index 00000000..8085b582 --- /dev/null +++ b/examples/omnilock_multisig_example.rs @@ -0,0 +1,88 @@ +use ckb_sdk::{ + constants::ONE_CKB, + transaction::{ + builder::{CkbTransactionBuilder, SimpleTransactionBuilder}, + handler::{omnilock, HandlerContexts}, + input::InputIterator, + signer::{SignContexts, TransactionSigner}, + TransactionBuilderConfiguration, + }, + unlock::{MultisigConfig, OmniLockConfig}, + Address, CkbRpcClient, NetworkInfo, +}; +use ckb_types::{ + h160, h256, + packed::CellOutput, + prelude::{Builder, Entity, Pack}, +}; +use std::{error::Error as StdErr, str::FromStr}; + +fn main() -> Result<(), Box> { + let network_info = NetworkInfo::testnet(); + let sender = Address::from_str("ckt1qrejnmlar3r452tcg57gvq8patctcgy8acync0hxfnyka35ywafvkqgxhjvp3k9pf88upngryvuxc346q7fq5qmlqqlrhr0p")?; + let receiver = Address::from_str("ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqv5dsed9par23x4g58seaw58j3ym5ml2hs8ztche")?; + + let configuration = TransactionBuilderConfiguration::new_with_network(network_info.clone())?; + + let iterator = InputIterator::new_with_address(&[sender.clone()], configuration.network_info()); + let mut builder = SimpleTransactionBuilder::new(configuration, iterator); + + let output = CellOutput::new_builder() + .capacity((128 * ONE_CKB).pack()) + .lock((&receiver).into()) + .build(); + builder.add_output_and_data(output.clone(), ckb_types::packed::Bytes::default()); + builder.set_change_lock((&sender).into()); + + let mut omni_cfg = OmniLockConfig::from_addr(&sender).unwrap(); + let multisig_config = MultisigConfig::new_with( + vec![ + h160!("0x7336b0ba900684cb3cb00f0d46d4f64c0994a562"), + h160!("0x5724c1e3925a5206944d753a6f3edaedf977d77f"), + ], + 0, + 2, + ) + .unwrap(); + omni_cfg.set_multisig_config(Some(multisig_config)); + let context = omnilock::OmnilockScriptContext::new(omni_cfg.clone(), network_info.url.clone()); + + let mut contexts = HandlerContexts::default(); + contexts.add_context(Box::new(context) as Box<_>); + + let mut tx_with_groups = builder.build(&contexts)?; + + let json_tx = ckb_jsonrpc_types::TransactionView::from(tx_with_groups.get_tx_view().clone()); + println!("tx: {}", serde_json::to_string_pretty(&json_tx).unwrap()); + + let signer = TransactionSigner::new(&network_info); + let private_key = h256!("0x7438f7b35c355e3d2fb9305167a31a72d22ddeafb80a21cc99ff6329d92e8087"); + signer.sign_transaction( + &mut tx_with_groups, + &SignContexts::new_omnilock( + [secp256k1::SecretKey::from_slice(private_key.as_bytes())?].to_vec(), + omni_cfg.clone(), + ckb_sdk::unlock::OmniUnlockMode::Normal, + ), + )?; + let private_key = h256!("0x4fd809631a6aa6e3bb378dd65eae5d71df895a82c91a615a1e8264741515c79c"); + signer.sign_transaction( + &mut tx_with_groups, + &SignContexts::new_omnilock( + [secp256k1::SecretKey::from_slice(private_key.as_bytes())?].to_vec(), + omni_cfg, + ckb_sdk::unlock::OmniUnlockMode::Normal, + ), + )?; + + let json_tx = ckb_jsonrpc_types::TransactionView::from(tx_with_groups.get_tx_view().clone()); + println!("tx: {}", serde_json::to_string_pretty(&json_tx).unwrap()); + + let tx_hash = CkbRpcClient::new(network_info.url.as_str()) + .send_transaction(json_tx.inner, None) + .expect("send transaction"); + // example tx: 3c5062f75f8c9dc799a3286ebef070cd3aa1b51575244c912076b90cb915a374 + println!(">>> tx {} sent! <<<", tx_hash); + + Ok(()) +} diff --git a/examples/chain_transfer_sighash.rs b/examples/transfer_chain_sighash.rs similarity index 100% rename from examples/chain_transfer_sighash.rs rename to examples/transfer_chain_sighash.rs diff --git a/rust-toolchain b/rust-toolchain deleted file mode 100644 index 7c7053aa..00000000 --- a/rust-toolchain +++ /dev/null @@ -1 +0,0 @@ -1.75.0 diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 00000000..7897a24d --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "1.75.0" diff --git a/src/constants.rs b/src/constants.rs index 59eca08b..1b479fb1 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -51,6 +51,11 @@ pub const ACP_TYPE_HASH_AGGRON: H256 = /// cheque withdraw since value pub const CHEQUE_CELL_SINCE: u64 = 0xA000000000000006; +pub const COMMON_PREFIX: &str = "CKB transaction: 0x"; +pub const BTC_PREFIX: &str = "CKB (Bitcoin Layer) transaction: 0x"; + +pub const SECP256K1_TAG_PUBKEY_UNCOMPRESSED: u8 = 0x04; + #[cfg(test)] mod test { use super::*; diff --git a/src/core/advanced_builders.rs b/src/core/advanced_builders.rs index 6b3a7889..fb348241 100644 --- a/src/core/advanced_builders.rs +++ b/src/core/advanced_builders.rs @@ -1,4 +1,8 @@ -use ckb_types::{constants, core, packed, prelude::*}; +use ckb_types::{ + constants, core, + packed::{self, WitnessArgs}, + prelude::*, +}; use derive_getters::Getters; /// An advanced builder for [`TransactionView`]. @@ -223,6 +227,27 @@ impl TransactionBuilder { set_output_data ); + fn get_witness_args(&self, i: usize) -> WitnessArgs { + let witness_data = self.witnesses[i].raw_data(); + if witness_data.is_empty() { + WitnessArgs::default() + } else { + WitnessArgs::from_slice(witness_data.as_ref()) + .expect("WitnessArgs expected but failed to decode ") + } + } + + pub fn set_witness_lock(&mut self, i: usize, v: Option) -> &mut Self { + let current_witness = self.get_witness_args(i); + self.witnesses[i] = current_witness + .as_builder() + .lock(v.pack()) + .build() + .as_bytes() + .pack(); + self + } + /// Converts into [`TransactionView`](struct.TransactionView.html). pub fn build(self) -> core::TransactionView { let Self { diff --git a/src/lib.rs b/src/lib.rs index f4745518..4854259f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,10 +9,8 @@ pub mod types; pub mod unlock; pub mod util; -#[cfg(feature = "test")] pub mod test_util; -#[cfg(feature = "test")] #[cfg(test)] mod tests; diff --git a/src/test-data/omni_lock b/src/test-data/omni_lock deleted file mode 100755 index 96c90c33..00000000 Binary files a/src/test-data/omni_lock and /dev/null differ diff --git a/src/test_util.rs b/src/test_util.rs index 77f5f1bc..e1b51f5b 100644 --- a/src/test_util.rs +++ b/src/test_util.rs @@ -79,7 +79,7 @@ impl Context { /// with secp256k1_data and map the script id to dep_group cell_dep. The /// contracts can only be referenced by data hash and with /// hash_type="data1". - pub fn new(block: &BlockView, contracts: Vec<(&[u8], bool)>) -> Context { + pub fn new(block: &BlockView, contracts: Vec<(&[u8], bool)>) -> (Context, Vec) { let block_number: u64 = block.number(); assert_eq!(block_number, 0); let cell_dep_resolver = DefaultCellDepResolver::from_genesis(block).expect("genesis info"); @@ -126,11 +126,14 @@ impl Context { } } + let mut contract_outpoints = Vec::with_capacity(contracts.len()); + if !contracts.is_empty() { let secp_data_out_point = OutPoint::new(block.transaction(0).unwrap().hash(), 3); for (bin, is_lock) in contracts { let data_hash = H256::from(blake2b_256(bin)); let out_point = ctx.deploy_cell(Bytes::from(bin.to_vec())); + contract_outpoints.push(out_point.clone()); if is_lock { let out_points: OutPointVec = vec![secp_data_out_point.clone(), out_point].pack(); @@ -145,14 +148,14 @@ impl Context { } } ctx.add_header(block.header()); - ctx + (ctx, contract_outpoints) } - /// Adds a live cell to the set. + /// Adds or updates a live cell in the set. /// - /// If the set did not have this input present, old live cell is returned. + /// If the set did not have this input present, None is returned. /// - /// If the set did have this input present, None is returned. + /// If the set did have this input present, the old live cell is returned. pub fn add_live_cell( &mut self, input: CellInput, diff --git a/src/tests/mod.rs b/src/tests/mod.rs index ac3d50b6..093f049f 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -1,3 +1,8 @@ +mod ckb_indexer_rpc; +mod ckb_rpc; +mod transaction; +mod tx_builder; + use std::{collections::HashMap, u64}; use ckb_dao_utils::pack_dao_data; @@ -7,7 +12,7 @@ use ckb_types::{ bytes::Bytes, core::{BlockView, Capacity, EpochNumberWithFraction, HeaderBuilder, ScriptHashType}, h160, h256, - packed::{CellInput, CellOutput, Script, ScriptOpt, WitnessArgs}, + packed::{CellInput, CellOutput, OutPoint, Script, ScriptOpt, WitnessArgs}, prelude::*, H160, H256, }; @@ -28,7 +33,7 @@ use crate::tx_builder::{ unlock_tx, CapacityBalancer, TransferAction, TxBuilder, }; use crate::unlock::{ - AcpUnlocker, ChequeAction, ChequeUnlocker, MultisigConfig, ScriptUnlocker, + AcpUnlocker, ChequeAction, ChequeUnlocker, MultisigConfig, OmniLockConfig, ScriptUnlocker, SecpMultisigUnlocker, SecpSighashUnlocker, }; use crate::util::{calculate_dao_maximum_withdraw4, minimal_unlock_point}; @@ -57,11 +62,18 @@ const ACCOUNT3_KEY: H256 = const ACCOUNT3_ARG: H160 = h160!("0xdabe88a65760c662ee3f07dee162409f7b20b694"); const FEE_RATE: u64 = 1000; -const GENESIS_JSON: &str = include_str!("../test-data/genesis_block.json"); -const SUDT_BIN: &[u8] = include_bytes!("../test-data/simple_udt"); -const ACP_BIN: &[u8] = include_bytes!("../test-data/anyone_can_pay"); -const CHEQUE_BIN: &[u8] = include_bytes!("../test-data/ckb-cheque-script"); -const ALWAYS_SUCCESS_BIN: &[u8] = include_bytes!("../test-data/always_success"); +const GENESIS_JSON: &str = include_str!("../../test-data/genesis_block.json"); +const SUDT_BIN: &[u8] = include_bytes!("../../test-data/simple_udt"); +const ACP_BIN: &[u8] = include_bytes!("../../test-data/anyone_can_pay"); +const CHEQUE_BIN: &[u8] = include_bytes!("../../test-data/ckb-cheque-script"); +const ALWAYS_SUCCESS_BIN: &[u8] = include_bytes!("../../test-data/always_success"); +// https://github.com/XuJiandong/ckb-production-scripts/commit/f692e01ead9378093b57b47023f3408e4c35349f +#[cfg(not(unix))] +const ALWAYS_SUCCESS_DL_BIN: &[u8] = include_bytes!("../../test-data/always_success_dl"); +const OMNILOCK_BIN: &[u8] = include_bytes!("../../test-data/omni_lock"); +// https://github.com/nervosnetwork/ckb-production-scripts/blob/410b16c499a8888781d9ab03160eeef93182d8e6/c/validate_signature_rsa.c +#[cfg(unix)] +const RSA_DL_BIN: &[u8] = include_bytes!("../../test-data/validate_signature_rsa"); fn build_sighash_script(args: H160) -> Script { Script::new_builder() @@ -79,6 +91,32 @@ fn build_multisig_script(cfg: &MultisigConfig) -> Script { .build() } +fn build_always_success_script() -> Script { + let data_hash = H256::from(blake2b_256(ALWAYS_SUCCESS_BIN)); + Script::new_builder() + .code_hash(data_hash.pack()) + .hash_type(ScriptHashType::Data1.into()) + .build() +} + +#[cfg(not(unix))] +fn build_always_success_dl_script() -> Script { + let data_hash = H256::from(blake2b_256(ALWAYS_SUCCESS_DL_BIN)); + Script::new_builder() + .code_hash(data_hash.pack()) + .hash_type(ScriptHashType::Data1.into()) + .build() +} + +#[cfg(unix)] +fn build_rsa_script_dl() -> Script { + let data_hash = H256::from(blake2b_256(RSA_DL_BIN)); + Script::new_builder() + .code_hash(data_hash.pack()) + .hash_type(ScriptHashType::Data1.into()) + .build() +} + fn build_dao_script() -> Script { Script::new_builder() .code_hash(DAO_TYPE_HASH.pack()) @@ -114,22 +152,34 @@ fn build_multisig_unlockers( unlockers } -fn init_context(contracts: Vec<(&[u8], bool)>, live_cells: Vec<(Script, Option)>) -> Context { +fn build_omnilock_script(cfg: &OmniLockConfig) -> Script { + let omnilock_data_hash = H256::from(blake2b_256(OMNILOCK_BIN)); + Script::new_builder() + .code_hash(omnilock_data_hash.pack()) + .hash_type(ScriptHashType::Data1.into()) + .args(cfg.build_args().pack()) + .build() +} + +fn init_context( + contracts: Vec<(&[u8], bool)>, + live_cells: Vec<(Script, Option)>, +) -> (Context, Vec) { // ckb-cli --url https://testnet.ckb.dev rpc get_block_by_number --number 0 --output-format json --raw-data > genensis_block.json let genesis_block: json_types::BlockView = serde_json::from_str(GENESIS_JSON).unwrap(); let genesis_block: BlockView = genesis_block.into(); - let mut ctx = Context::new(&genesis_block, contracts); + let (mut ctx, outpoints) = Context::new(&genesis_block, contracts); for (lock, capacity_opt) in live_cells { ctx.add_simple_live_cell(random_out_point(), lock, capacity_opt); } - ctx + (ctx, outpoints) } #[test] fn test_transfer_from_sighash() { let sender = build_sighash_script(ACCOUNT1_ARG); let receiver = build_sighash_script(ACCOUNT2_ARG); - let ctx = init_context( + let (ctx, _) = init_context( Vec::new(), vec![ (sender.clone(), Some(100 * ONE_CKB)), @@ -186,7 +236,7 @@ fn test_transfer_from_sighash() { fn test_transfer_capacity_overflow() { let sender = build_sighash_script(ACCOUNT1_ARG); let receiver = build_sighash_script(ACCOUNT2_ARG); - let ctx = init_context(Vec::new(), vec![(sender.clone(), Some(100 * ONE_CKB))]); + let (ctx, _) = init_context(Vec::new(), vec![(sender.clone(), Some(100 * ONE_CKB))]); let large_amount: u64 = u64::MAX; let output = CellOutput::new_builder() @@ -219,7 +269,7 @@ fn test_transfer_from_multisig() { let sender = build_multisig_script(&cfg); let receiver = build_sighash_script(ACCOUNT2_ARG); - let ctx = init_context( + let (ctx, _) = init_context( Vec::new(), vec![ (sender.clone(), Some(100 * ONE_CKB)), @@ -283,7 +333,7 @@ fn test_transfer_from_acp() { .args(Bytes::from(ACCOUNT1_ARG.0.to_vec()).pack()) .build(); let receiver = build_sighash_script(ACCOUNT2_ARG); - let ctx = init_context( + let (ctx, _) = init_context( vec![(ACP_BIN, true)], vec![ (sender.clone(), Some(100 * ONE_CKB)), @@ -344,7 +394,7 @@ fn test_transfer_to_acp() { .hash_type(ScriptHashType::Data1.into()) .args(Bytes::from(ACCOUNT2_ARG.0.to_vec()).pack()) .build(); - let ctx = init_context( + let (ctx, _) = init_context( vec![(ACP_BIN, true)], vec![ (sender.clone(), Some(100 * ONE_CKB)), @@ -430,7 +480,7 @@ fn test_cheque_claim() { .hash_type(ScriptHashType::Data1.into()) .args(Bytes::from(vec![9u8; 32]).pack()) .build(); - let mut ctx = init_context( + let (mut ctx, _) = init_context( vec![(CHEQUE_BIN, true), (SUDT_BIN, false)], vec![ (receiver.clone(), Some(100 * ONE_CKB)), @@ -550,7 +600,7 @@ fn test_cheque_withdraw() { .hash_type(ScriptHashType::Data1.into()) .args(Bytes::from(vec![9u8; 32]).pack()) .build(); - let mut ctx = init_context( + let (mut ctx, _) = init_context( vec![(CHEQUE_BIN, true), (SUDT_BIN, false)], vec![ (sender.clone(), Some(100 * ONE_CKB)), @@ -635,7 +685,7 @@ fn test_cheque_withdraw() { #[test] fn test_dao_deposit() { let sender = build_sighash_script(ACCOUNT1_ARG); - let ctx = init_context( + let (ctx, _) = init_context( Vec::new(), vec![ (sender.clone(), Some(100 * ONE_CKB)), @@ -700,7 +750,7 @@ fn test_dao_deposit() { #[test] fn test_dao_prepare() { let sender = build_sighash_script(ACCOUNT1_ARG); - let mut ctx = init_context( + let (mut ctx, _) = init_context( Vec::new(), vec![ (sender.clone(), Some(100 * ONE_CKB)), @@ -789,7 +839,7 @@ fn test_dao_prepare() { #[test] fn test_dao_withdraw() { let sender = build_sighash_script(ACCOUNT1_ARG); - let mut ctx = init_context( + let (mut ctx, _) = init_context( Vec::new(), vec![ (sender.clone(), Some(100 * ONE_CKB)), @@ -929,7 +979,7 @@ fn test_udt_issue() { let sudt_data_hash = H256::from(blake2b_256(SUDT_BIN)); let owner = build_sighash_script(ACCOUNT1_ARG); let receiver = build_sighash_script(ACCOUNT2_ARG); - let ctx = init_context( + let (ctx, _) = init_context( vec![(SUDT_BIN, false)], vec![ (owner.clone(), Some(100 * ONE_CKB)), @@ -1023,7 +1073,7 @@ fn test_udt_transfer() { .hash_type(ScriptHashType::Data1.into()) .args(owner.calc_script_hash().as_bytes().pack()) .build(); - let mut ctx = init_context( + let (mut ctx, _) = init_context( vec![(ACP_BIN, true), (SUDT_BIN, false)], vec![ (sender.clone(), Some(100 * ONE_CKB)), @@ -1110,10 +1160,3 @@ fn test_udt_transfer() { ); ctx.verify(tx, FEE_RATE).unwrap(); } - -pub mod ckb_indexer_rpc; -pub mod ckb_rpc; -pub mod cycle; -pub mod omni_lock; -pub mod omni_lock_util; -pub mod transaction; diff --git a/src/tests/transaction/mod.rs b/src/tests/transaction/mod.rs index ac42796e..2dc406d0 100644 --- a/src/tests/transaction/mod.rs +++ b/src/tests/transaction/mod.rs @@ -1,2 +1,3 @@ -pub mod sighash; -pub mod typeid; +mod omnilock; +mod sighash; +mod typeid; diff --git a/src/tests/transaction/omnilock.rs b/src/tests/transaction/omnilock.rs new file mode 100644 index 00000000..59af55a5 --- /dev/null +++ b/src/tests/transaction/omnilock.rs @@ -0,0 +1,2354 @@ +use std::convert::TryInto; + +use ckb_crypto::secp::{Pubkey, SECP256K1}; +use ckb_hash::blake2b_256; +use ckb_types::{ + core::{DepType, TransactionView}, + packed::{CellOutput, OutPoint}, + prelude::*, + H160, H256, +}; + +use crate::{ + constants::ONE_CKB, + tests::{ + build_always_success_script, build_omnilock_script, build_sighash_script, init_context, + random_out_point, ACCOUNT0_ARG, ACCOUNT0_KEY, ACCOUNT1_ARG, ACCOUNT1_KEY, ACCOUNT2_ARG, + ACCOUNT2_KEY, ACCOUNT3_ARG, ACCOUNT3_KEY, ALWAYS_SUCCESS_BIN, FEE_RATE, OMNILOCK_BIN, + }, + traits::{LiveCell, Signer, SignerError}, + transaction::{ + builder::{CkbTransactionBuilder, SimpleTransactionBuilder}, + handler::{ + multisig::Secp256k1Blake160MultisigAllScriptHandler, + omnilock::{OmnilockScriptContext, OmnilockScriptHandler}, + sighash::Secp256k1Blake160SighashAllScriptHandler, + typeid::TypeIdHandler, + HandlerContexts, + }, + input::InputIterator, + signer::{SignContexts, TransactionSigner}, + TransactionBuilderConfiguration, + }, + types::xudt_rce_mol::SmtProofEntryVec, + unlock::{ + omni_lock::{AdminConfig, ExecDlConfig, Identity, Preimage}, + IdentityFlag, MultisigConfig, OmniLockAcpConfig, OmniLockConfig, OmniUnlockMode, + }, + util::{blake160, btc_auth, eos_auth, keccak160}, + NetworkInfo, +}; + +fn test_omnilock_config(outpoints: Vec) -> TransactionBuilderConfiguration { + let network_info = NetworkInfo::testnet(); + let mut configuration = + TransactionBuilderConfiguration::new_with_empty_handlers(network_info.clone()); + let mut omni_lock_handler = OmnilockScriptHandler::new_with_network(&network_info).unwrap(); + + omni_lock_handler.set_lock_script_id(crate::ScriptId::new_data1(H256::from(blake2b_256( + OMNILOCK_BIN, + )))); + let dep_cells = { + let mut cells = Vec::with_capacity(outpoints.len()); + for outpoint in outpoints { + cells.push( + ckb_types::packed::CellDep::new_builder() + .out_point(outpoint) + .dep_type(DepType::Code.into()) + .build(), + ) + } + cells + }; + omni_lock_handler.set_cell_deps(dep_cells); + + configuration.register_script_handler(Box::new( + Secp256k1Blake160SighashAllScriptHandler::new_with_network(&network_info).unwrap(), + )); + configuration.register_script_handler(Box::new( + Secp256k1Blake160MultisigAllScriptHandler::new_with_network(&network_info).unwrap(), + )); + configuration.register_script_handler(Box::new(TypeIdHandler)); + configuration.register_script_handler(Box::new(omni_lock_handler)); + + configuration +} + +#[test] +fn test_omnilock_ethereum() { + let account0_key = secp256k1::SecretKey::from_slice(ACCOUNT0_KEY.as_bytes()).unwrap(); + let pubkey = secp256k1::PublicKey::from_secret_key(&SECP256K1, &account0_key); + let mut cfg = OmniLockConfig::new_ethereum(keccak160(Pubkey::from(pubkey).as_ref())); + let sign_context = + SignContexts::new_omnilock(vec![account0_key], cfg.clone(), OmniUnlockMode::Normal); + omnilock_test(cfg.clone(), &sign_context); + + cfg.enable_cobuild(true); + let sign_context_2 = + SignContexts::new_omnilock(vec![account0_key], cfg.clone(), OmniUnlockMode::Normal); + omnilock_test(cfg, &sign_context_2); +} + +#[test] +fn test_omnilock_pubkeyhash() { + let account0_key = secp256k1::SecretKey::from_slice(ACCOUNT0_KEY.as_bytes()).unwrap(); + let pubkey = secp256k1::PublicKey::from_secret_key(&SECP256K1, &account0_key); + let mut cfg = OmniLockConfig::new_pubkey_hash(blake160(&pubkey.serialize())); + + let sign_context = + SignContexts::new_omnilock(vec![account0_key], cfg.clone(), OmniUnlockMode::Normal); + omnilock_test(cfg.clone(), &sign_context); + + cfg.enable_cobuild(true); + let sign_context_2 = + SignContexts::new_omnilock(vec![account0_key], cfg.clone(), OmniUnlockMode::Normal); + omnilock_test(cfg, &sign_context_2); +} + +#[test] +fn test_omnilock_multisign() { + let account0_key = secp256k1::SecretKey::from_slice(ACCOUNT0_KEY.as_bytes()).unwrap(); + let account1_key = secp256k1::SecretKey::from_slice(ACCOUNT1_KEY.as_bytes()).unwrap(); + let lock_args = vec![ + ACCOUNT0_ARG.clone(), + ACCOUNT1_ARG.clone(), + ACCOUNT2_ARG.clone(), + ]; + let multi_cfg = MultisigConfig::new_with(lock_args, 0, 2).unwrap(); + let mut cfg = OmniLockConfig::new_multisig(multi_cfg); + let sign_context = SignContexts::new_omnilock( + vec![account0_key, account1_key], + cfg.clone(), + OmniUnlockMode::Normal, + ); + omnilock_test(cfg.clone(), &sign_context); + + cfg.enable_cobuild(true); + let sign_context_2 = SignContexts::new_omnilock( + vec![account0_key, account1_key], + cfg.clone(), + OmniUnlockMode::Normal, + ); + omnilock_test(cfg, &sign_context_2); +} + +#[test] +fn test_omnilock_ethereum_display() { + let account0_key = secp256k1::SecretKey::from_slice(ACCOUNT0_KEY.as_bytes()).unwrap(); + let pubkey = secp256k1::PublicKey::from_secret_key(&SECP256K1, &account0_key); + let mut cfg = OmniLockConfig::new_ethereum_display(keccak160(Pubkey::from(pubkey).as_ref())); + let sign_context = + SignContexts::new_omnilock(vec![account0_key], cfg.clone(), OmniUnlockMode::Normal); + omnilock_test(cfg.clone(), &sign_context); + + cfg.enable_cobuild(true); + let sign_context_2 = + SignContexts::new_omnilock(vec![account0_key], cfg.clone(), OmniUnlockMode::Normal); + omnilock_test(cfg, &sign_context_2); +} + +#[test] +fn test_omnilock_btc() { + let account0_key = secp256k1::SecretKey::from_slice(ACCOUNT0_KEY.as_bytes()).unwrap(); + let pubkey = secp256k1::PublicKey::from_secret_key(&SECP256K1, &account0_key); + // uncompressed + let mut cfg = OmniLockConfig::new_btc_from_pubkey( + &Pubkey::from(pubkey), + crate::unlock::omni_lock::BTCSignVtype::P2PKHUncompressed, + ); + let sign_context = + SignContexts::new_omnilock(vec![account0_key], cfg.clone(), OmniUnlockMode::Normal); + omnilock_test(cfg.clone(), &sign_context); + + cfg.enable_cobuild(true); + let sign_context_2 = + SignContexts::new_omnilock(vec![account0_key], cfg.clone(), OmniUnlockMode::Normal); + omnilock_test(cfg, &sign_context_2); + + // compressed + let mut cfg = OmniLockConfig::new_btc_from_pubkey( + &Pubkey::from(pubkey), + crate::unlock::omni_lock::BTCSignVtype::P2PKHCompressed, + ); + let sign_context = + SignContexts::new_omnilock(vec![account0_key], cfg.clone(), OmniUnlockMode::Normal); + omnilock_test(cfg.clone(), &sign_context); + + cfg.enable_cobuild(true); + let sign_context_2 = + SignContexts::new_omnilock(vec![account0_key], cfg.clone(), OmniUnlockMode::Normal); + omnilock_test(cfg, &sign_context_2); + + // segwitp2sh + let mut cfg = OmniLockConfig::new_btc_from_pubkey( + &Pubkey::from(pubkey), + crate::unlock::omni_lock::BTCSignVtype::SegwitP2SH, + ); + let sign_context = + SignContexts::new_omnilock(vec![account0_key], cfg.clone(), OmniUnlockMode::Normal); + omnilock_test(cfg.clone(), &sign_context); + + cfg.enable_cobuild(true); + let sign_context_2 = + SignContexts::new_omnilock(vec![account0_key], cfg.clone(), OmniUnlockMode::Normal); + omnilock_test(cfg, &sign_context_2); + + // segwitbech32 + let mut cfg = OmniLockConfig::new_btc_from_pubkey( + &Pubkey::from(pubkey), + crate::unlock::omni_lock::BTCSignVtype::SegwitBech32, + ); + let sign_context = + SignContexts::new_omnilock(vec![account0_key], cfg.clone(), OmniUnlockMode::Normal); + omnilock_test(cfg.clone(), &sign_context); + + cfg.enable_cobuild(true); + let sign_context_2 = + SignContexts::new_omnilock(vec![account0_key], cfg.clone(), OmniUnlockMode::Normal); + omnilock_test(cfg, &sign_context_2); +} + +#[test] +fn test_omnilock_dog() { + let account0_key = secp256k1::SecretKey::from_slice(ACCOUNT0_KEY.as_bytes()).unwrap(); + let pubkey = secp256k1::PublicKey::from_secret_key(&SECP256K1, &account0_key); + + // uncompress + let mut cfg = OmniLockConfig::new_dogcoin_from_pubkey( + &Pubkey::from(pubkey), + crate::unlock::omni_lock::BTCSignVtype::P2PKHUncompressed, + ); + let sign_context = + SignContexts::new_omnilock(vec![account0_key], cfg.clone(), OmniUnlockMode::Normal); + omnilock_test(cfg.clone(), &sign_context); + + cfg.enable_cobuild(true); + let sign_context_2 = + SignContexts::new_omnilock(vec![account0_key], cfg.clone(), OmniUnlockMode::Normal); + omnilock_test(cfg, &sign_context_2); + + // compress + let mut cfg = OmniLockConfig::new_dogcoin_from_pubkey( + &Pubkey::from(pubkey), + crate::unlock::omni_lock::BTCSignVtype::P2PKHCompressed, + ); + let sign_context = + SignContexts::new_omnilock(vec![account0_key], cfg.clone(), OmniUnlockMode::Normal); + omnilock_test(cfg.clone(), &sign_context); + + cfg.enable_cobuild(true); + let sign_context_2 = + SignContexts::new_omnilock(vec![account0_key], cfg.clone(), OmniUnlockMode::Normal); + omnilock_test(cfg, &sign_context_2); + + // SegwitP2SH + let mut cfg = OmniLockConfig::new_dogcoin_from_pubkey( + &Pubkey::from(pubkey), + crate::unlock::omni_lock::BTCSignVtype::SegwitP2SH, + ); + let sign_context = + SignContexts::new_omnilock(vec![account0_key], cfg.clone(), OmniUnlockMode::Normal); + omnilock_test(cfg.clone(), &sign_context); + + cfg.enable_cobuild(true); + let sign_context_2 = + SignContexts::new_omnilock(vec![account0_key], cfg.clone(), OmniUnlockMode::Normal); + omnilock_test(cfg, &sign_context_2); + + // SegwitBech32 + let mut cfg = OmniLockConfig::new_dogcoin_from_pubkey( + &Pubkey::from(pubkey), + crate::unlock::omni_lock::BTCSignVtype::SegwitBech32, + ); + let sign_context = + SignContexts::new_omnilock(vec![account0_key], cfg.clone(), OmniUnlockMode::Normal); + omnilock_test(cfg.clone(), &sign_context); + + cfg.enable_cobuild(true); + let sign_context_2 = + SignContexts::new_omnilock(vec![account0_key], cfg.clone(), OmniUnlockMode::Normal); + omnilock_test(cfg, &sign_context_2); +} + +#[test] +fn test_omnilock_eos() { + let account0_key = secp256k1::SecretKey::from_slice(ACCOUNT0_KEY.as_bytes()).unwrap(); + let pubkey = secp256k1::PublicKey::from_secret_key(&SECP256K1, &account0_key); + + // uncompressed + let mut cfg = OmniLockConfig::new_eos_from_pubkey( + &Pubkey::from(pubkey), + crate::unlock::omni_lock::BTCSignVtype::P2PKHUncompressed, + ); + let sign_context = + SignContexts::new_omnilock(vec![account0_key], cfg.clone(), OmniUnlockMode::Normal); + omnilock_test(cfg.clone(), &sign_context); + + cfg.enable_cobuild(true); + let sign_context_2 = + SignContexts::new_omnilock(vec![account0_key], cfg.clone(), OmniUnlockMode::Normal); + omnilock_test(cfg, &sign_context_2); + + // compressed + let mut cfg = OmniLockConfig::new_eos_from_pubkey( + &Pubkey::from(pubkey), + crate::unlock::omni_lock::BTCSignVtype::P2PKHCompressed, + ); + let sign_context = + SignContexts::new_omnilock(vec![account0_key], cfg.clone(), OmniUnlockMode::Normal); + omnilock_test(cfg.clone(), &sign_context); + + cfg.enable_cobuild(true); + let sign_context_2 = + SignContexts::new_omnilock(vec![account0_key], cfg.clone(), OmniUnlockMode::Normal); + omnilock_test(cfg, &sign_context_2); +} + +#[test] +fn test_omnilock_tron() { + let account0_key = secp256k1::SecretKey::from_slice(ACCOUNT0_KEY.as_bytes()).unwrap(); + let pubkey = secp256k1::PublicKey::from_secret_key(&SECP256K1, &account0_key); + let mut cfg = OmniLockConfig::new_tron_from_pubkey(&Pubkey::from(pubkey)); + let sign_context = + SignContexts::new_omnilock(vec![account0_key], cfg.clone(), OmniUnlockMode::Normal); + omnilock_test(cfg.clone(), &sign_context); + + cfg.enable_cobuild(true); + let sign_context_2 = + SignContexts::new_omnilock(vec![account0_key], cfg.clone(), OmniUnlockMode::Normal); + omnilock_test(cfg, &sign_context_2); +} + +#[test] +fn test_omnilock_solana() { + let account0_key = + ed25519_dalek::SigningKey::from_bytes(&ACCOUNT0_KEY.as_bytes().try_into().unwrap()); + let pubkey = account0_key.verifying_key(); + let mut cfg = OmniLockConfig::new_solana_from_pubkey(&pubkey); + let sign_context = SignContexts::new_omnilock_solana( + vec![account0_key.clone()], + cfg.clone(), + OmniUnlockMode::Normal, + ); + omnilock_test(cfg.clone(), &sign_context); + + cfg.enable_cobuild(true); + let sign_context_2 = + SignContexts::new_omnilock_solana(vec![account0_key], cfg.clone(), OmniUnlockMode::Normal); + omnilock_test(cfg, &sign_context_2); +} + +fn omnilock_test(cfg: OmniLockConfig, sign_context: &SignContexts) { + let network_info = NetworkInfo::testnet(); + + let sender = build_omnilock_script(&cfg); + let receiver = build_sighash_script(ACCOUNT2_ARG); + + let (ctx, outpoints) = init_context( + vec![(OMNILOCK_BIN, true)], + vec![ + (sender.clone(), Some(100 * ONE_CKB)), + (sender.clone(), Some(200 * ONE_CKB)), + (sender.clone(), Some(300 * ONE_CKB)), + ], + ); + + let configuration = test_omnilock_config(outpoints); + + let iterator = InputIterator::new_with_cell_collector( + vec![sender.clone()], + Box::new(ctx.to_live_cells_context()) as Box<_>, + ); + let mut builder = SimpleTransactionBuilder::new(configuration, iterator); + + let output = CellOutput::new_builder() + .capacity((120 * ONE_CKB).pack()) + .lock(receiver) + .build(); + builder.add_output_and_data(output.clone(), ckb_types::packed::Bytes::default()); + builder.set_change_lock(sender.clone()); + + let context = OmnilockScriptContext::new(cfg.clone(), network_info.url.clone()); + let mut contexts = HandlerContexts::default(); + contexts.add_context(Box::new(context) as Box<_>); + + let mut tx_with_groups = builder.build(&contexts).expect("build failed"); + + // let json_tx = ckb_jsonrpc_types::TransactionView::from(tx_with_groups.get_tx_view().clone()); + // println!("tx: {}", serde_json::to_string_pretty(&json_tx).unwrap()); + + TransactionSigner::new(&network_info) + // use unitest lock to verify + .insert_unlocker( + crate::ScriptId::new_data1(H256::from(blake2b_256(OMNILOCK_BIN))), + crate::transaction::signer::omnilock::OmnilockSigner {}, + ) + .sign_transaction(&mut tx_with_groups, sign_context) + .unwrap(); + + // let json_tx = ckb_jsonrpc_types::TransactionView::from(tx_with_groups.get_tx_view().clone()); + // println!("tx: {}", serde_json::to_string_pretty(&json_tx).unwrap()); + + let tx = tx_with_groups.get_tx_view().clone(); + let script_groups = tx_with_groups.script_groups.clone(); + assert_eq!(script_groups.len(), 1); + assert_eq!(tx.header_deps().len(), 0); + assert_eq!(tx.cell_deps().len(), 1); + assert_eq!(tx.inputs().len(), 2); + for out_point in tx.input_pts_iter() { + assert_eq!(ctx.get_input(&out_point).unwrap().0.lock(), sender); + } + assert_eq!(tx.outputs().len(), 2); + assert_eq!(tx.output(0).unwrap(), output); + assert_eq!(tx.output(1).unwrap().lock(), sender); + let change_capacity: u64 = tx.output(1).unwrap().capacity().unpack(); + let fee = (100 + 200 - 120) * ONE_CKB - change_capacity; + assert_eq!(tx.data().as_reader().serialized_size_in_block() as u64, fee); + + ctx.verify(tx, FEE_RATE).unwrap(); +} + +#[test] +fn test_omnilock_owner_lock() { + test_omnilock_owner_lock_tranfer(false); + test_omnilock_owner_lock_tranfer(true) +} + +fn test_omnilock_owner_lock_tranfer(cobuild: bool) { + let network_info = NetworkInfo::testnet(); + let receiver = build_sighash_script(ACCOUNT2_ARG); + let sender1 = build_sighash_script(ACCOUNT0_ARG); + let account0_key = secp256k1::SecretKey::from_slice(ACCOUNT0_KEY.as_bytes()).unwrap(); + let hash = H160::from_slice(&sender1.calc_script_hash().as_slice()[0..20]).unwrap(); + let mut cfg = OmniLockConfig::new_ownerlock(hash); + cfg.enable_cobuild(cobuild); + let sender0 = build_omnilock_script(&cfg); + let mut sign_context = + SignContexts::new_omnilock(vec![account0_key], cfg.clone(), OmniUnlockMode::Normal); + let hashall_unlock = + crate::transaction::signer::sighash::Secp256k1Blake160SighashAllSignerContext::new(vec![ + account0_key, + ]); + sign_context.add_context(Box::new(hashall_unlock)); + + let (ctx, outpoints) = init_context( + vec![(OMNILOCK_BIN, true)], + vec![ + (sender0.clone(), Some(150 * ONE_CKB)), + (sender1.clone(), Some(61 * ONE_CKB)), + ], + ); + + let configuration = test_omnilock_config(outpoints); + let iterator = InputIterator::new_with_cell_collector( + vec![sender0.clone(), sender1.clone()], + Box::new(ctx.to_live_cells_context()) as Box<_>, + ); + let mut builder = SimpleTransactionBuilder::new(configuration, iterator); + let output = CellOutput::new_builder() + .capacity((110 * ONE_CKB).pack()) + .lock(receiver.clone()) + .build(); + builder.add_output_and_data(output.clone(), ckb_types::packed::Bytes::default()); + builder.set_change_lock(sender0.clone()); + + let context = OmnilockScriptContext::new(cfg.clone(), network_info.url.clone()); + let mut contexts = HandlerContexts::default(); + contexts.add_context(Box::new(context) as Box<_>); + + let mut tx_with_groups = builder.build(&contexts).expect("build failed"); + + // let json_tx = ckb_jsonrpc_types::TransactionView::from(tx_with_groups.get_tx_view().clone()); + // println!("tx: {}", serde_json::to_string_pretty(&json_tx).unwrap()); + + TransactionSigner::new(&network_info) + // use unitest lock to verify + .insert_unlocker( + crate::ScriptId::new_data1(H256::from(blake2b_256(OMNILOCK_BIN))), + crate::transaction::signer::omnilock::OmnilockSigner {}, + ) + .sign_transaction(&mut tx_with_groups, &sign_context) + .unwrap(); + + let tx = tx_with_groups.get_tx_view().clone(); + // let json_tx = ckb_jsonrpc_types::TransactionView::from(tx_with_groups.get_tx_view().clone()); + // println!("tx: {}", serde_json::to_string_pretty(&json_tx).unwrap()); + + let script_groups = tx_with_groups.script_groups.clone(); + assert_eq!(script_groups.len(), 2); + assert_eq!(tx.header_deps().len(), 0); + assert_eq!(tx.cell_deps().len(), 2); + assert_eq!(tx.inputs().len(), 2); + let mut senders = vec![sender0.clone(), sender1.clone()]; + for out_point in tx.input_pts_iter() { + let sender = ctx.get_input(&out_point).unwrap().0.lock(); + assert!(senders.contains(&sender)); + senders.retain(|x| x != &sender); + } + assert_eq!(tx.outputs().len(), 2); + assert_eq!(tx.output(0).unwrap(), output); + assert_eq!(tx.output(1).unwrap().lock(), sender0); + let change_capacity: u64 = tx.output(1).unwrap().capacity().unpack(); + let fee = (150 + 61 - 110) * ONE_CKB - change_capacity; + assert_eq!(tx.data().as_reader().serialized_size_in_block() as u64, fee); + ctx.verify(tx, FEE_RATE).unwrap(); +} + +#[cfg(unix)] +mod rsa_dl_test { + use super::test_omnilock_dl_exec; + use crate::{ + tests::build_rsa_script_dl, + traits::{Signer, SignerError}, + unlock::omni_lock::{ExecDlConfig, Preimage}, + util::blake160, + }; + + use ckb_types::core::TransactionView; + use openssl::{ + hash::MessageDigest, + pkey::{PKey, Private, Public}, + rsa::Rsa, + sign::Signer as RSASigner, + }; + + #[derive(Clone)] + struct RSASinger { + key: PKey, + } + + impl Signer for RSASinger { + fn match_id(&self, id: &[u8]) -> bool { + let rsa_script = build_rsa_script_dl(); + let public_key_pem: Vec = self.key.public_key_to_pem().unwrap(); + let rsa_pubkey = PKey::public_key_from_pem(&public_key_pem).unwrap(); + let signning_pubkey = rsa_signning_prepare_pubkey(&rsa_pubkey); + + let preimage = Preimage::new_with_dl(rsa_script, blake160(&signning_pubkey)); + id.len() == 20 && id == preimage.auth().as_bytes() + } + + fn sign( + &self, + id: &[u8], + message: &[u8], + _recoverable: bool, + _tx: &TransactionView, + ) -> Result { + if !self.match_id(id) { + return Err(SignerError::IdNotFound); + } + Ok(bytes::Bytes::from(rsa_sign(message, &self.key))) + } + } + + fn rsa_signning_prepare_pubkey(pubkey: &PKey) -> Vec { + let mut sig = vec![ + 1, // algorithm id + 1, // key size, 1024 + 0, // padding, PKCS# 1.5 + 6, // hash type SHA256 + ]; + + let pubkey2 = pubkey.rsa().unwrap(); + let mut e = pubkey2.e().to_vec(); + let mut n = pubkey2.n().to_vec(); + e.reverse(); + n.reverse(); + + while e.len() < 4 { + e.push(0); + } + while n.len() < 128 { + n.push(0); + } + sig.append(&mut e); // 4 bytes E + sig.append(&mut n); // N + + sig + } + + pub fn rsa_sign(msg: &[u8], key: &PKey) -> Vec { + let pem: Vec = key.public_key_to_pem().unwrap(); + let pubkey = PKey::public_key_from_pem(&pem).unwrap(); + + let mut sig = rsa_signning_prepare_pubkey(&pubkey); + + let mut signer = RSASigner::new(MessageDigest::sha256(), key).unwrap(); + signer.update(msg).unwrap(); + sig.extend(signer.sign_to_vec().unwrap()); // sig + + sig + } + + #[test] + fn test_omnilock_dl() { + let rsa_script = build_rsa_script_dl(); + let bits = 1024; + let rsa = Rsa::generate(bits).unwrap(); + let rsa_private_key = PKey::from_rsa(rsa).unwrap(); + let public_key_pem: Vec = rsa_private_key.public_key_to_pem().unwrap(); + let rsa_pubkey = PKey::public_key_from_pem(&public_key_pem).unwrap(); + let signning_pubkey = rsa_signning_prepare_pubkey(&rsa_pubkey); + + let preimage = Preimage::new_with_dl(rsa_script, blake160(&signning_pubkey)); + let config = ExecDlConfig::new(preimage, 264); + let signer = RSASinger { + key: rsa_private_key, + }; + test_omnilock_dl_exec(config.clone(), signer.clone(), false); + test_omnilock_dl_exec(config, signer.clone(), true); + } +} + +#[derive(Clone)] +struct DummySinger {} + +impl Signer for DummySinger { + fn match_id(&self, id: &[u8]) -> bool { + let (preimage, preimage_dl) = if cfg!(unix) { + let always_success_script = build_always_success_script(); + + ( + Preimage::new_with_exec( + always_success_script.clone(), + 0, + [0; 8], + blake160(&[0u8; 20]), + ), + Preimage::new_with_exec(always_success_script, 0, [0; 8], blake160(&[0u8; 20])), + ) + } else { + #[cfg(not(unix))] + { + use crate::tests::build_always_success_dl_script; + let always_success_script_dl = build_always_success_dl_script(); + let always_success_script = build_always_success_script(); + ( + Preimage::new_with_exec( + always_success_script.clone(), + 0, + [0; 8], + blake160(&[0u8; 20]), + ), + Preimage::new_with_dl(always_success_script_dl, H160::from([0u8; 20])), + ) + } + #[cfg(unix)] + unreachable!() + }; + + id.len() == 20 && (id == preimage.auth().as_bytes() || id == preimage_dl.auth().as_bytes()) + } + + fn sign( + &self, + id: &[u8], + _message: &[u8], + _recoverable: bool, + _tx: &TransactionView, + ) -> Result { + if !self.match_id(id) { + return Err(SignerError::IdNotFound); + } + Ok(bytes::Bytes::from(vec![0; 65])) + } +} + +#[test] +fn test_omnilock_exec() { + let always_success_script = build_always_success_script(); + let preimage = Preimage::new_with_exec(always_success_script, 0, [0; 8], blake160(&[0u8; 20])); + let config = ExecDlConfig::new(preimage, 65); + + test_omnilock_dl_exec(config.clone(), DummySinger {}, false); + test_omnilock_dl_exec(config, DummySinger {}, true) +} + +#[cfg(not(unix))] +#[test] +fn test_omnilock_dl() { + use crate::tests::build_always_success_dl_script; + let always_success_script = build_always_success_dl_script(); + let preimage = Preimage::new_with_dl(always_success_script, H160::from([0u8; 20])); + let config = ExecDlConfig::new(preimage, 65); + + test_omnilock_dl_exec(config.clone(), DummySinger {}, false); + test_omnilock_dl_exec(config, DummySinger {}, true) +} + +#[cfg(unix)] +fn dl_exec_cfg(config: ExecDlConfig) -> (OmniLockConfig, &'static [u8]) { + use crate::tests::RSA_DL_BIN; + if config.preimage().len() == 32 + 1 + 1 + 8 + 20 { + ( + OmniLockConfig::new_with_exec_preimage(config), + ALWAYS_SUCCESS_BIN, + ) + } else { + (OmniLockConfig::new_with_dl_preimage(config), RSA_DL_BIN) + } +} + +#[cfg(not(unix))] +fn dl_exec_cfg(config: ExecDlConfig) -> (OmniLockConfig, &'static [u8]) { + use crate::tests::ALWAYS_SUCCESS_DL_BIN; + if config.preimage().len() == 32 + 1 + 1 + 8 + 20 { + ( + OmniLockConfig::new_with_exec_preimage(config), + ALWAYS_SUCCESS_BIN, + ) + } else { + ( + OmniLockConfig::new_with_dl_preimage(config), + ALWAYS_SUCCESS_DL_BIN, + ) + } +} + +fn test_omnilock_dl_exec(config: ExecDlConfig, signer: T, cobuild: bool) { + let network_info = NetworkInfo::testnet(); + let receiver = build_sighash_script(ACCOUNT2_ARG); + + let (mut cfg, bin) = dl_exec_cfg(config); + + cfg.enable_cobuild(cobuild); + let sender = build_omnilock_script(&cfg); + let sign_context = + SignContexts::new_omnilock_exec_dl_custom(signer, cfg.clone(), OmniUnlockMode::Normal); + + let (ctx, outpoints) = init_context( + vec![(OMNILOCK_BIN, true), (bin, true)], + vec![(sender.clone(), Some(300 * ONE_CKB))], + ); + + let configuration = test_omnilock_config(outpoints); + + let iterator = InputIterator::new_with_cell_collector( + vec![sender.clone()], + Box::new(ctx.to_live_cells_context()) as Box<_>, + ); + let mut builder = SimpleTransactionBuilder::new(configuration, iterator); + + let output = CellOutput::new_builder() + .capacity((120 * ONE_CKB).pack()) + .lock(receiver) + .build(); + builder.add_output_and_data(output.clone(), ckb_types::packed::Bytes::default()); + builder.set_change_lock(sender.clone()); + + let context = OmnilockScriptContext::new(cfg.clone(), network_info.url.clone()); + let mut contexts = HandlerContexts::default(); + contexts.add_context(Box::new(context) as Box<_>); + + let mut tx_with_groups = builder.build(&contexts).expect("build failed"); + + // let json_tx = ckb_jsonrpc_types::TransactionView::from(tx_with_groups.get_tx_view().clone()); + // println!("tx: {}", serde_json::to_string_pretty(&json_tx).unwrap()); + + TransactionSigner::new(&network_info) + // use unitest lock to verify + .insert_unlocker( + crate::ScriptId::new_data1(H256::from(blake2b_256(OMNILOCK_BIN))), + crate::transaction::signer::omnilock::OmnilockSigner {}, + ) + .sign_transaction(&mut tx_with_groups, &sign_context) + .unwrap(); + let tx = tx_with_groups.get_tx_view().clone(); + let script_groups = tx_with_groups.script_groups.clone(); + + // let json_tx = ckb_jsonrpc_types::TransactionView::from(tx_with_groups.get_tx_view().clone()); + // println!("tx: {}", serde_json::to_string_pretty(&json_tx).unwrap()); + + assert_eq!(script_groups.len(), 1); + assert_eq!(tx.header_deps().len(), 0); + assert_eq!(tx.cell_deps().len(), 2); + assert_eq!(tx.inputs().len(), 1); + for out_point in tx.input_pts_iter() { + assert_eq!(ctx.get_input(&out_point).unwrap().0.lock(), sender); + } + assert_eq!(tx.outputs().len(), 2); + assert_eq!(tx.output(0).unwrap(), output); + assert_eq!(tx.output(1).unwrap().lock(), sender); + let change_capacity: u64 = tx.output(1).unwrap().capacity().unpack(); + let fee = (300 - 120) * ONE_CKB - change_capacity; + assert_eq!(tx.data().as_reader().serialized_size_in_block() as u64, fee); + ctx.verify(tx, FEE_RATE).unwrap(); +} + +#[test] +fn test_omnilock_pubkeyhash_rc_dep() { + let sender_key = secp256k1::SecretKey::from_slice(ACCOUNT0_KEY.as_bytes()) + .map_err(|err| format!("invalid sender secret key: {}", err)) + .unwrap(); + let pubkey = secp256k1::PublicKey::from_secret_key(&SECP256K1, &sender_key); + let pubkey_hash = blake160(&pubkey.serialize()); + let mut cfg = OmniLockConfig::new_pubkey_hash(pubkey_hash); + + let account3_key = secp256k1::SecretKey::from_slice(ACCOUNT3_KEY.as_bytes()) + .map_err(|err| format!("invalid sender secret key: {}", err)) + .unwrap(); + let pubkey = secp256k1::PublicKey::from_secret_key(&SECP256K1, &account3_key); + let id = Identity::new_pubkey_hash(blake160(&pubkey.serialize())); + + cfg.set_admin_config(AdminConfig::new( + H256::default(), + SmtProofEntryVec::default(), + id, + None, + false, + )); + test_omnilock_rc_dep(cfg.clone(), OmniUnlockMode::Admin); + test_omnilock_rc_dep(cfg.clone(), OmniUnlockMode::Normal); + cfg.enable_cobuild(true); + test_omnilock_rc_dep(cfg.clone(), OmniUnlockMode::Admin); + test_omnilock_rc_dep(cfg, OmniUnlockMode::Normal); +} + +#[test] +fn test_omnilock_ethereum_rc_dep() { + let account0_key = secp256k1::SecretKey::from_slice(ACCOUNT0_KEY.as_bytes()) + .map_err(|err| format!("invalid sender secret key: {}", err)) + .unwrap(); + let pubkey = secp256k1::PublicKey::from_secret_key(&SECP256K1, &account0_key); + let mut cfg = OmniLockConfig::new_ethereum(keccak160(Pubkey::from(pubkey).as_ref())); + + let account3_key = secp256k1::SecretKey::from_slice(ACCOUNT3_KEY.as_bytes()) + .map_err(|err| format!("invalid sender secret key: {}", err)) + .unwrap(); + let pubkey = secp256k1::PublicKey::from_secret_key(&SECP256K1, &account3_key); + let id = Identity::new_ethereum(keccak160(Pubkey::from(pubkey).as_ref())); + cfg.set_admin_config(AdminConfig::new( + H256::default(), + SmtProofEntryVec::default(), + id, + None, + false, + )); + test_omnilock_rc_dep(cfg.clone(), OmniUnlockMode::Admin); + test_omnilock_rc_dep(cfg.clone(), OmniUnlockMode::Normal); + cfg.enable_cobuild(true); + test_omnilock_rc_dep(cfg.clone(), OmniUnlockMode::Admin); + test_omnilock_rc_dep(cfg, OmniUnlockMode::Normal); +} + +#[test] +fn test_omnilock_ethereum_dispaly_rc_dep() { + let account0_key = secp256k1::SecretKey::from_slice(ACCOUNT0_KEY.as_bytes()) + .map_err(|err| format!("invalid sender secret key: {}", err)) + .unwrap(); + let pubkey = secp256k1::PublicKey::from_secret_key(&SECP256K1, &account0_key); + let mut cfg = OmniLockConfig::new_ethereum_display(keccak160(Pubkey::from(pubkey).as_ref())); + + let account3_key = secp256k1::SecretKey::from_slice(ACCOUNT3_KEY.as_bytes()) + .map_err(|err| format!("invalid sender secret key: {}", err)) + .unwrap(); + let pubkey = secp256k1::PublicKey::from_secret_key(&SECP256K1, &account3_key); + let id = Identity::new_ethereum_display(keccak160(Pubkey::from(pubkey).as_ref())); + cfg.set_admin_config(AdminConfig::new( + H256::default(), + SmtProofEntryVec::default(), + id, + None, + false, + )); + test_omnilock_rc_dep(cfg.clone(), OmniUnlockMode::Admin); + test_omnilock_rc_dep(cfg.clone(), OmniUnlockMode::Normal); + cfg.enable_cobuild(true); + test_omnilock_rc_dep(cfg.clone(), OmniUnlockMode::Admin); + test_omnilock_rc_dep(cfg, OmniUnlockMode::Normal); +} + +#[test] +fn test_omnilock_btc_rc_dep() { + let account0_key = secp256k1::SecretKey::from_slice(ACCOUNT0_KEY.as_bytes()).unwrap(); + let pubkey_0 = secp256k1::PublicKey::from_secret_key(&SECP256K1, &account0_key); + // uncompressed + let mut cfg = OmniLockConfig::new_btc_from_pubkey( + &Pubkey::from(pubkey_0), + crate::unlock::omni_lock::BTCSignVtype::P2PKHUncompressed, + ); + + let account3_key = secp256k1::SecretKey::from_slice(ACCOUNT3_KEY.as_bytes()) + .map_err(|err| format!("invalid sender secret key: {}", err)) + .unwrap(); + let pubkey_3 = secp256k1::PublicKey::from_secret_key(&SECP256K1, &account3_key); + let id = Identity::new_btc(H160::from(btc_auth( + &pubkey_3.into(), + crate::unlock::omni_lock::BTCSignVtype::P2PKHUncompressed, + ))); + + cfg.set_admin_config(AdminConfig::new( + H256::default(), + SmtProofEntryVec::default(), + id, + None, + false, + )); + test_omnilock_rc_dep(cfg.clone(), OmniUnlockMode::Admin); + test_omnilock_rc_dep(cfg.clone(), OmniUnlockMode::Normal); + cfg.enable_cobuild(true); + test_omnilock_rc_dep(cfg.clone(), OmniUnlockMode::Admin); + test_omnilock_rc_dep(cfg, OmniUnlockMode::Normal); + + // compressed + let mut cfg = OmniLockConfig::new_btc_from_pubkey( + &Pubkey::from(pubkey_0), + crate::unlock::omni_lock::BTCSignVtype::P2PKHCompressed, + ); + let id = Identity::new_btc(H160::from(btc_auth( + &pubkey_3.into(), + crate::unlock::omni_lock::BTCSignVtype::P2PKHCompressed, + ))); + + cfg.set_admin_config(AdminConfig::new( + H256::default(), + SmtProofEntryVec::default(), + id, + None, + false, + )); + test_omnilock_rc_dep(cfg.clone(), OmniUnlockMode::Admin); + test_omnilock_rc_dep(cfg.clone(), OmniUnlockMode::Normal); + cfg.enable_cobuild(true); + test_omnilock_rc_dep(cfg.clone(), OmniUnlockMode::Admin); + test_omnilock_rc_dep(cfg, OmniUnlockMode::Normal); + + // SegwitBech32 + let mut cfg = OmniLockConfig::new_btc_from_pubkey( + &Pubkey::from(pubkey_0), + crate::unlock::omni_lock::BTCSignVtype::SegwitBech32, + ); + let id = Identity::new_btc(H160::from(btc_auth( + &pubkey_3.into(), + crate::unlock::omni_lock::BTCSignVtype::SegwitBech32, + ))); + + cfg.set_admin_config(AdminConfig::new( + H256::default(), + SmtProofEntryVec::default(), + id, + None, + false, + )); + test_omnilock_rc_dep(cfg.clone(), OmniUnlockMode::Admin); + test_omnilock_rc_dep(cfg.clone(), OmniUnlockMode::Normal); + cfg.enable_cobuild(true); + test_omnilock_rc_dep(cfg.clone(), OmniUnlockMode::Admin); + test_omnilock_rc_dep(cfg, OmniUnlockMode::Normal); + + // SegwitP2SH + let mut cfg = OmniLockConfig::new_btc_from_pubkey( + &Pubkey::from(pubkey_0), + crate::unlock::omni_lock::BTCSignVtype::SegwitP2SH, + ); + let id = Identity::new_btc(H160::from(btc_auth( + &pubkey_3.into(), + crate::unlock::omni_lock::BTCSignVtype::SegwitP2SH, + ))); + + cfg.set_admin_config(AdminConfig::new( + H256::default(), + SmtProofEntryVec::default(), + id, + None, + false, + )); + test_omnilock_rc_dep(cfg.clone(), OmniUnlockMode::Admin); + test_omnilock_rc_dep(cfg.clone(), OmniUnlockMode::Normal); + cfg.enable_cobuild(true); + test_omnilock_rc_dep(cfg.clone(), OmniUnlockMode::Admin); + test_omnilock_rc_dep(cfg, OmniUnlockMode::Normal); +} + +#[test] +fn test_omnilock_dog_rc_dep() { + let account0_key = secp256k1::SecretKey::from_slice(ACCOUNT0_KEY.as_bytes()).unwrap(); + let pubkey_0 = secp256k1::PublicKey::from_secret_key(&SECP256K1, &account0_key); + // uncompressed + let mut cfg = OmniLockConfig::new_dogcoin_from_pubkey( + &Pubkey::from(pubkey_0), + crate::unlock::omni_lock::BTCSignVtype::P2PKHUncompressed, + ); + + let account3_key = secp256k1::SecretKey::from_slice(ACCOUNT3_KEY.as_bytes()) + .map_err(|err| format!("invalid sender secret key: {}", err)) + .unwrap(); + let pubkey_3 = secp256k1::PublicKey::from_secret_key(&SECP256K1, &account3_key); + let id = Identity::new_dogcoin(H160::from(btc_auth( + &pubkey_3.into(), + crate::unlock::omni_lock::BTCSignVtype::P2PKHUncompressed, + ))); + + cfg.set_admin_config(AdminConfig::new( + H256::default(), + SmtProofEntryVec::default(), + id, + None, + false, + )); + test_omnilock_rc_dep(cfg.clone(), OmniUnlockMode::Admin); + test_omnilock_rc_dep(cfg.clone(), OmniUnlockMode::Normal); + cfg.enable_cobuild(true); + test_omnilock_rc_dep(cfg.clone(), OmniUnlockMode::Admin); + test_omnilock_rc_dep(cfg, OmniUnlockMode::Normal); + + // compressed + let mut cfg = OmniLockConfig::new_dogcoin_from_pubkey( + &Pubkey::from(pubkey_0), + crate::unlock::omni_lock::BTCSignVtype::P2PKHCompressed, + ); + let id = Identity::new_dogcoin(H160::from(btc_auth( + &pubkey_3.into(), + crate::unlock::omni_lock::BTCSignVtype::P2PKHCompressed, + ))); + + cfg.set_admin_config(AdminConfig::new( + H256::default(), + SmtProofEntryVec::default(), + id, + None, + false, + )); + test_omnilock_rc_dep(cfg.clone(), OmniUnlockMode::Admin); + test_omnilock_rc_dep(cfg.clone(), OmniUnlockMode::Normal); + cfg.enable_cobuild(true); + test_omnilock_rc_dep(cfg.clone(), OmniUnlockMode::Admin); + test_omnilock_rc_dep(cfg, OmniUnlockMode::Normal); + + // SegwitBech32 + let mut cfg = OmniLockConfig::new_dogcoin_from_pubkey( + &Pubkey::from(pubkey_0), + crate::unlock::omni_lock::BTCSignVtype::SegwitBech32, + ); + let id = Identity::new_dogcoin(H160::from(btc_auth( + &pubkey_3.into(), + crate::unlock::omni_lock::BTCSignVtype::SegwitBech32, + ))); + + cfg.set_admin_config(AdminConfig::new( + H256::default(), + SmtProofEntryVec::default(), + id, + None, + false, + )); + test_omnilock_rc_dep(cfg.clone(), OmniUnlockMode::Admin); + test_omnilock_rc_dep(cfg.clone(), OmniUnlockMode::Normal); + cfg.enable_cobuild(true); + test_omnilock_rc_dep(cfg.clone(), OmniUnlockMode::Admin); + test_omnilock_rc_dep(cfg, OmniUnlockMode::Normal); + + // SegwitP2SH + let mut cfg = OmniLockConfig::new_dogcoin_from_pubkey( + &Pubkey::from(pubkey_0), + crate::unlock::omni_lock::BTCSignVtype::SegwitP2SH, + ); + let id = Identity::new_dogcoin(H160::from(btc_auth( + &pubkey_3.into(), + crate::unlock::omni_lock::BTCSignVtype::SegwitP2SH, + ))); + + cfg.set_admin_config(AdminConfig::new( + H256::default(), + SmtProofEntryVec::default(), + id, + None, + false, + )); + test_omnilock_rc_dep(cfg.clone(), OmniUnlockMode::Admin); + test_omnilock_rc_dep(cfg.clone(), OmniUnlockMode::Normal); + cfg.enable_cobuild(true); + test_omnilock_rc_dep(cfg.clone(), OmniUnlockMode::Admin); + test_omnilock_rc_dep(cfg, OmniUnlockMode::Normal); +} + +#[test] +fn test_omnilock_eos_rc_dep() { + let account0_key = secp256k1::SecretKey::from_slice(ACCOUNT0_KEY.as_bytes()).unwrap(); + let pubkey_0 = secp256k1::PublicKey::from_secret_key(&SECP256K1, &account0_key); + // uncompressed + let mut cfg = OmniLockConfig::new_eos_from_pubkey( + &Pubkey::from(pubkey_0), + crate::unlock::omni_lock::BTCSignVtype::P2PKHUncompressed, + ); + + let account3_key = secp256k1::SecretKey::from_slice(ACCOUNT3_KEY.as_bytes()) + .map_err(|err| format!("invalid sender secret key: {}", err)) + .unwrap(); + let pubkey_3 = secp256k1::PublicKey::from_secret_key(&SECP256K1, &account3_key); + let id = Identity::new_eos(H160::from(eos_auth( + &pubkey_3.into(), + crate::unlock::omni_lock::BTCSignVtype::P2PKHUncompressed, + ))); + + cfg.set_admin_config(AdminConfig::new( + H256::default(), + SmtProofEntryVec::default(), + id, + None, + false, + )); + test_omnilock_rc_dep(cfg.clone(), OmniUnlockMode::Admin); + test_omnilock_rc_dep(cfg.clone(), OmniUnlockMode::Normal); + cfg.enable_cobuild(true); + test_omnilock_rc_dep(cfg.clone(), OmniUnlockMode::Admin); + test_omnilock_rc_dep(cfg, OmniUnlockMode::Normal); + + // compressed + let mut cfg = OmniLockConfig::new_eos_from_pubkey( + &Pubkey::from(pubkey_0), + crate::unlock::omni_lock::BTCSignVtype::P2PKHCompressed, + ); + let id = Identity::new_eos(H160::from(eos_auth( + &pubkey_3.into(), + crate::unlock::omni_lock::BTCSignVtype::P2PKHCompressed, + ))); + + cfg.set_admin_config(AdminConfig::new( + H256::default(), + SmtProofEntryVec::default(), + id, + None, + false, + )); + test_omnilock_rc_dep(cfg.clone(), OmniUnlockMode::Admin); + test_omnilock_rc_dep(cfg.clone(), OmniUnlockMode::Normal); + cfg.enable_cobuild(true); + test_omnilock_rc_dep(cfg.clone(), OmniUnlockMode::Admin); + test_omnilock_rc_dep(cfg, OmniUnlockMode::Normal); +} + +#[test] +fn test_omnilock_tron_rc_dep() { + let account0_key = secp256k1::SecretKey::from_slice(ACCOUNT0_KEY.as_bytes()).unwrap(); + let pubkey_0 = secp256k1::PublicKey::from_secret_key(&SECP256K1, &account0_key); + + let mut cfg = OmniLockConfig::new_tron_from_pubkey(&Pubkey::from(pubkey_0)); + + let account3_key = secp256k1::SecretKey::from_slice(ACCOUNT3_KEY.as_bytes()) + .map_err(|err| format!("invalid sender secret key: {}", err)) + .unwrap(); + let pubkey_3 = secp256k1::PublicKey::from_secret_key(&SECP256K1, &account3_key); + let id = Identity::new_tron(keccak160(Pubkey::from(pubkey_3).as_bytes())); + + cfg.set_admin_config(AdminConfig::new( + H256::default(), + SmtProofEntryVec::default(), + id, + None, + false, + )); + test_omnilock_rc_dep(cfg.clone(), OmniUnlockMode::Admin); + test_omnilock_rc_dep(cfg.clone(), OmniUnlockMode::Normal); + cfg.enable_cobuild(true); + test_omnilock_rc_dep(cfg.clone(), OmniUnlockMode::Admin); + test_omnilock_rc_dep(cfg, OmniUnlockMode::Normal); +} + +#[test] +fn test_omnilock_solana_rc_dep() { + let account0_key = + ed25519_dalek::SigningKey::from_bytes(ACCOUNT0_KEY.as_bytes().try_into().unwrap()); + let pubkey_0 = account0_key.verifying_key(); + + let mut cfg = OmniLockConfig::new_solana_from_pubkey(&pubkey_0); + + let account3_key = + ed25519_dalek::SigningKey::from_bytes(ACCOUNT3_KEY.as_bytes().try_into().unwrap()); + let pubkey_3 = account3_key.verifying_key(); + let id = Identity::new( + crate::unlock::IdentityFlag::Solana, + blake160(pubkey_3.as_bytes()), + ); + + cfg.set_admin_config(AdminConfig::new( + H256::default(), + SmtProofEntryVec::default(), + id, + None, + false, + )); + test_omnilock_rc_dep(cfg.clone(), OmniUnlockMode::Admin); + test_omnilock_rc_dep(cfg.clone(), OmniUnlockMode::Normal); + cfg.enable_cobuild(true); + test_omnilock_rc_dep(cfg.clone(), OmniUnlockMode::Admin); + test_omnilock_rc_dep(cfg, OmniUnlockMode::Normal); +} + +fn test_omnilock_rc_dep(mut cfg: OmniLockConfig, unlock_mode: OmniUnlockMode) { + let network_info = NetworkInfo::testnet(); + let receiver = build_sighash_script(ACCOUNT2_ARG); + + let (mut ctx, mut outpoints) = init_context(vec![(OMNILOCK_BIN, true)], vec![]); + let (rce_cells, rce_cells_len) = match unlock_mode { + OmniUnlockMode::Admin => { + let mut admin_config = cfg.get_admin_config().unwrap().clone(); + let rc_args = match unlock_mode { + OmniUnlockMode::Admin => ACCOUNT3_ARG, + OmniUnlockMode::Normal => ACCOUNT0_ARG, + }; + let (proof_vec, rc_type_id, rce_cells) = + crate::tests::tx_builder::omni_lock_util::generate_rc( + &mut ctx, + admin_config.get_auth().to_smt_key().into(), + false, + rc_args, + ); + admin_config.set_proofs(proof_vec); + admin_config.set_rc_type_id(H256::from_slice(rc_type_id.as_ref()).unwrap()); + cfg.set_admin_config(admin_config); + let rce_cells_len = rce_cells.len(); + (Some(rce_cells), rce_cells_len) + } + OmniUnlockMode::Normal => (None, 0), + }; + let sender = build_omnilock_script(&cfg); + for (lock, capacity_opt) in [ + (sender.clone(), Some(300 * ONE_CKB)), + (sender.clone(), Some(300 * ONE_CKB)), + ] { + ctx.add_simple_live_cell(random_out_point(), lock, capacity_opt); + } + + outpoints.extend(rce_cells.unwrap_or_default()); + + let configuration = test_omnilock_config(outpoints); + + let iterator = InputIterator::new_with_cell_collector( + vec![sender.clone()], + Box::new(ctx.to_live_cells_context()) as Box<_>, + ); + let mut builder = SimpleTransactionBuilder::new(configuration, iterator); + + let output = CellOutput::new_builder() + .capacity((110 * ONE_CKB).pack()) + .lock(receiver) + .build(); + + builder.add_output_and_data(output.clone(), ckb_types::packed::Bytes::default()); + builder.set_change_lock(sender.clone()); + + let unlock_key = match unlock_mode { + OmniUnlockMode::Admin => ACCOUNT3_KEY, + OmniUnlockMode::Normal => ACCOUNT0_KEY, + }; + + let sign_context = if cfg.id().flag() == IdentityFlag::Solana { + SignContexts::new_omnilock_solana( + vec![ed25519_dalek::SigningKey::from_bytes( + unlock_key.as_bytes().try_into().unwrap(), + )], + cfg.clone(), + unlock_mode, + ) + } else { + SignContexts::new_omnilock( + vec![secp256k1::SecretKey::from_slice(unlock_key.as_bytes()).unwrap()], + cfg.clone(), + unlock_mode, + ) + }; + + let context = + OmnilockScriptContext::new(cfg.clone(), network_info.url.clone()).unlock_mode(unlock_mode); + let mut contexts = HandlerContexts::default(); + contexts.add_context(Box::new(context) as Box<_>); + + let mut tx_with_groups = builder.build(&contexts).expect("build failed"); + + // let json_tx = ckb_jsonrpc_types::TransactionView::from(tx_with_groups.get_tx_view().clone()); + // println!("tx: {}", serde_json::to_string_pretty(&json_tx).unwrap()); + + TransactionSigner::new(&network_info) + // use unitest lock to verify + .insert_unlocker( + crate::ScriptId::new_data1(H256::from(blake2b_256(OMNILOCK_BIN))), + crate::transaction::signer::omnilock::OmnilockSigner {}, + ) + .sign_transaction(&mut tx_with_groups, &sign_context) + .unwrap(); + let tx = tx_with_groups.get_tx_view().clone(); + let script_groups = tx_with_groups.script_groups.clone(); + + // let json_tx = ckb_jsonrpc_types::TransactionView::from(tx_with_groups.get_tx_view().clone()); + // println!("tx: {}", serde_json::to_string_pretty(&json_tx).unwrap()); + + assert_eq!(script_groups.len(), 1); + assert_eq!(tx.header_deps().len(), 0); + assert_eq!(tx.cell_deps().len(), 1 + rce_cells_len); + assert_eq!(tx.inputs().len(), 1); + for out_point in tx.input_pts_iter() { + assert_eq!(ctx.get_input(&out_point).unwrap().0.lock(), sender); + } + assert_eq!(tx.outputs().len(), 2); + assert_eq!(tx.output(0).unwrap(), output); + assert_eq!(tx.output(1).unwrap().lock(), sender); + let change_capacity: u64 = tx.output(1).unwrap().capacity().unpack(); + let fee = (300 - 110) * ONE_CKB - change_capacity; + assert_eq!(tx.data().as_reader().serialized_size_in_block() as u64, fee); + + ctx.verify(tx, FEE_RATE).unwrap(); +} + +#[test] +fn test_omnilock_pubkeyhash_rc_input_and_dep() { + let sender_key = secp256k1::SecretKey::from_slice(ACCOUNT0_KEY.as_bytes()) + .map_err(|err| format!("invalid sender secret key: {}", err)) + .unwrap(); + let pubkey = secp256k1::PublicKey::from_secret_key(&SECP256K1, &sender_key); + let pubkey_hash = blake160(&pubkey.serialize()); + let mut cfg = OmniLockConfig::new_pubkey_hash(pubkey_hash); + + let account3_key = secp256k1::SecretKey::from_slice(ACCOUNT3_KEY.as_bytes()) + .map_err(|err| format!("invalid sender secret key: {}", err)) + .unwrap(); + let pubkey = secp256k1::PublicKey::from_secret_key(&SECP256K1, &account3_key); + let id = Identity::new_pubkey_hash(blake160(&pubkey.serialize())); + cfg.set_admin_config(AdminConfig::new( + H256::default(), + SmtProofEntryVec::default(), + id.clone(), + None, + false, + )); + test_omnilock_rc_input_and_dep(cfg.clone()); + cfg.enable_cobuild(true); + test_omnilock_rc_input_and_dep(cfg.clone()); + + cfg.set_admin_config(AdminConfig::new( + H256::default(), + SmtProofEntryVec::default(), + id, + None, + true, + )); + test_omnilock_rc_input_and_dep(cfg.clone()); + cfg.enable_cobuild(true); + test_omnilock_rc_input_and_dep(cfg); +} + +#[test] +fn test_omnilock_eth_rc_input_and_dep() { + let sender_key = secp256k1::SecretKey::from_slice(ACCOUNT0_KEY.as_bytes()) + .map_err(|err| format!("invalid sender secret key: {}", err)) + .unwrap(); + let pubkey = secp256k1::PublicKey::from_secret_key(&SECP256K1, &sender_key); + let mut cfg = OmniLockConfig::new_ethereum(keccak160(Pubkey::from(pubkey).as_ref())); + + let account3_key = secp256k1::SecretKey::from_slice(ACCOUNT3_KEY.as_bytes()) + .map_err(|err| format!("invalid sender secret key: {}", err)) + .unwrap(); + let pubkey = secp256k1::PublicKey::from_secret_key(&SECP256K1, &account3_key); + let id = Identity::new_ethereum(keccak160(Pubkey::from(pubkey).as_ref())); + cfg.set_admin_config(AdminConfig::new( + H256::default(), + SmtProofEntryVec::default(), + id.clone(), + None, + false, + )); + test_omnilock_rc_input_and_dep(cfg.clone()); + cfg.enable_cobuild(true); + test_omnilock_rc_input_and_dep(cfg.clone()); + + cfg.set_admin_config(AdminConfig::new( + H256::default(), + SmtProofEntryVec::default(), + id, + None, + true, + )); + test_omnilock_rc_input_and_dep(cfg.clone()); + cfg.enable_cobuild(true); + test_omnilock_rc_input_and_dep(cfg); +} + +#[test] +fn test_omnilock_eth_display_rc_input_and_dep() { + let sender_key = secp256k1::SecretKey::from_slice(ACCOUNT0_KEY.as_bytes()) + .map_err(|err| format!("invalid sender secret key: {}", err)) + .unwrap(); + let pubkey = secp256k1::PublicKey::from_secret_key(&SECP256K1, &sender_key); + let mut cfg = OmniLockConfig::new_ethereum_display(keccak160(Pubkey::from(pubkey).as_ref())); + + let account3_key = secp256k1::SecretKey::from_slice(ACCOUNT3_KEY.as_bytes()) + .map_err(|err| format!("invalid sender secret key: {}", err)) + .unwrap(); + let pubkey = secp256k1::PublicKey::from_secret_key(&SECP256K1, &account3_key); + let id = Identity::new_ethereum_display(keccak160(Pubkey::from(pubkey).as_ref())); + cfg.set_admin_config(AdminConfig::new( + H256::default(), + SmtProofEntryVec::default(), + id.clone(), + None, + false, + )); + test_omnilock_rc_input_and_dep(cfg.clone()); + cfg.enable_cobuild(true); + test_omnilock_rc_input_and_dep(cfg.clone()); + + cfg.set_admin_config(AdminConfig::new( + H256::default(), + SmtProofEntryVec::default(), + id, + None, + true, + )); + test_omnilock_rc_input_and_dep(cfg.clone()); + cfg.enable_cobuild(true); + test_omnilock_rc_input_and_dep(cfg); +} + +#[test] +fn test_omnilock_btc_rc_input_and_dep() { + let account0_key = secp256k1::SecretKey::from_slice(ACCOUNT0_KEY.as_bytes()).unwrap(); + let pubkey_0 = secp256k1::PublicKey::from_secret_key(&SECP256K1, &account0_key); + // uncompressed + let mut cfg = OmniLockConfig::new_btc_from_pubkey( + &Pubkey::from(pubkey_0), + crate::unlock::omni_lock::BTCSignVtype::P2PKHUncompressed, + ); + + let account3_key = secp256k1::SecretKey::from_slice(ACCOUNT3_KEY.as_bytes()) + .map_err(|err| format!("invalid sender secret key: {}", err)) + .unwrap(); + let pubkey_3 = secp256k1::PublicKey::from_secret_key(&SECP256K1, &account3_key); + let id = Identity::new_btc(H160::from(btc_auth( + &pubkey_3.into(), + crate::unlock::omni_lock::BTCSignVtype::P2PKHUncompressed, + ))); + + cfg.set_admin_config(AdminConfig::new( + H256::default(), + SmtProofEntryVec::default(), + id.clone(), + None, + false, + )); + test_omnilock_rc_input_and_dep(cfg.clone()); + cfg.enable_cobuild(true); + test_omnilock_rc_input_and_dep(cfg.clone()); + + cfg.set_admin_config(AdminConfig::new( + H256::default(), + SmtProofEntryVec::default(), + id, + None, + true, + )); + test_omnilock_rc_input_and_dep(cfg.clone()); + cfg.enable_cobuild(true); + test_omnilock_rc_input_and_dep(cfg); + + // P2PKHCompressed + let mut cfg = OmniLockConfig::new_btc_from_pubkey( + &Pubkey::from(pubkey_0), + crate::unlock::omni_lock::BTCSignVtype::P2PKHCompressed, + ); + let id = Identity::new_btc(H160::from(btc_auth( + &pubkey_3.into(), + crate::unlock::omni_lock::BTCSignVtype::P2PKHCompressed, + ))); + cfg.set_admin_config(AdminConfig::new( + H256::default(), + SmtProofEntryVec::default(), + id.clone(), + None, + false, + )); + test_omnilock_rc_input_and_dep(cfg.clone()); + cfg.enable_cobuild(true); + test_omnilock_rc_input_and_dep(cfg.clone()); + + cfg.set_admin_config(AdminConfig::new( + H256::default(), + SmtProofEntryVec::default(), + id, + None, + true, + )); + test_omnilock_rc_input_and_dep(cfg.clone()); + cfg.enable_cobuild(true); + test_omnilock_rc_input_and_dep(cfg); + + // SegwitBech32 + let mut cfg = OmniLockConfig::new_btc_from_pubkey( + &Pubkey::from(pubkey_0), + crate::unlock::omni_lock::BTCSignVtype::SegwitBech32, + ); + let id = Identity::new_btc(H160::from(btc_auth( + &pubkey_3.into(), + crate::unlock::omni_lock::BTCSignVtype::SegwitBech32, + ))); + cfg.set_admin_config(AdminConfig::new( + H256::default(), + SmtProofEntryVec::default(), + id.clone(), + None, + false, + )); + test_omnilock_rc_input_and_dep(cfg.clone()); + cfg.enable_cobuild(true); + test_omnilock_rc_input_and_dep(cfg.clone()); + + cfg.set_admin_config(AdminConfig::new( + H256::default(), + SmtProofEntryVec::default(), + id, + None, + true, + )); + test_omnilock_rc_input_and_dep(cfg.clone()); + cfg.enable_cobuild(true); + test_omnilock_rc_input_and_dep(cfg); + + // SegwitP2SH + let mut cfg = OmniLockConfig::new_btc_from_pubkey( + &Pubkey::from(pubkey_0), + crate::unlock::omni_lock::BTCSignVtype::SegwitP2SH, + ); + let id = Identity::new_btc(H160::from(btc_auth( + &pubkey_3.into(), + crate::unlock::omni_lock::BTCSignVtype::SegwitP2SH, + ))); + cfg.set_admin_config(AdminConfig::new( + H256::default(), + SmtProofEntryVec::default(), + id.clone(), + None, + false, + )); + test_omnilock_rc_input_and_dep(cfg.clone()); + cfg.enable_cobuild(true); + test_omnilock_rc_input_and_dep(cfg.clone()); + + cfg.set_admin_config(AdminConfig::new( + H256::default(), + SmtProofEntryVec::default(), + id, + None, + true, + )); + test_omnilock_rc_input_and_dep(cfg.clone()); + cfg.enable_cobuild(true); + test_omnilock_rc_input_and_dep(cfg); +} + +#[test] +fn test_omnilock_dog_rc_input_and_dep() { + let account0_key = secp256k1::SecretKey::from_slice(ACCOUNT0_KEY.as_bytes()).unwrap(); + let pubkey_0 = secp256k1::PublicKey::from_secret_key(&SECP256K1, &account0_key); + // uncompressed + let mut cfg = OmniLockConfig::new_dogcoin_from_pubkey( + &Pubkey::from(pubkey_0), + crate::unlock::omni_lock::BTCSignVtype::P2PKHUncompressed, + ); + + let account3_key = secp256k1::SecretKey::from_slice(ACCOUNT3_KEY.as_bytes()) + .map_err(|err| format!("invalid sender secret key: {}", err)) + .unwrap(); + let pubkey_3 = secp256k1::PublicKey::from_secret_key(&SECP256K1, &account3_key); + let id = Identity::new_dogcoin(H160::from(btc_auth( + &pubkey_3.into(), + crate::unlock::omni_lock::BTCSignVtype::P2PKHUncompressed, + ))); + + cfg.set_admin_config(AdminConfig::new( + H256::default(), + SmtProofEntryVec::default(), + id.clone(), + None, + false, + )); + test_omnilock_rc_input_and_dep(cfg.clone()); + cfg.enable_cobuild(true); + test_omnilock_rc_input_and_dep(cfg.clone()); + + cfg.set_admin_config(AdminConfig::new( + H256::default(), + SmtProofEntryVec::default(), + id, + None, + true, + )); + test_omnilock_rc_input_and_dep(cfg.clone()); + cfg.enable_cobuild(true); + test_omnilock_rc_input_and_dep(cfg); + + // P2PKHCompressed + let mut cfg = OmniLockConfig::new_dogcoin_from_pubkey( + &Pubkey::from(pubkey_0), + crate::unlock::omni_lock::BTCSignVtype::P2PKHCompressed, + ); + let id = Identity::new_dogcoin(H160::from(btc_auth( + &pubkey_3.into(), + crate::unlock::omni_lock::BTCSignVtype::P2PKHCompressed, + ))); + cfg.set_admin_config(AdminConfig::new( + H256::default(), + SmtProofEntryVec::default(), + id.clone(), + None, + false, + )); + test_omnilock_rc_input_and_dep(cfg.clone()); + cfg.enable_cobuild(true); + test_omnilock_rc_input_and_dep(cfg.clone()); + + cfg.set_admin_config(AdminConfig::new( + H256::default(), + SmtProofEntryVec::default(), + id, + None, + true, + )); + test_omnilock_rc_input_and_dep(cfg.clone()); + cfg.enable_cobuild(true); + test_omnilock_rc_input_and_dep(cfg); + + // SegwitBech32 + let mut cfg = OmniLockConfig::new_dogcoin_from_pubkey( + &Pubkey::from(pubkey_0), + crate::unlock::omni_lock::BTCSignVtype::SegwitBech32, + ); + let id = Identity::new_dogcoin(H160::from(btc_auth( + &pubkey_3.into(), + crate::unlock::omni_lock::BTCSignVtype::SegwitBech32, + ))); + cfg.set_admin_config(AdminConfig::new( + H256::default(), + SmtProofEntryVec::default(), + id.clone(), + None, + false, + )); + test_omnilock_rc_input_and_dep(cfg.clone()); + cfg.enable_cobuild(true); + test_omnilock_rc_input_and_dep(cfg.clone()); + + cfg.set_admin_config(AdminConfig::new( + H256::default(), + SmtProofEntryVec::default(), + id, + None, + true, + )); + test_omnilock_rc_input_and_dep(cfg.clone()); + cfg.enable_cobuild(true); + test_omnilock_rc_input_and_dep(cfg); + + // SegwitP2SH + let mut cfg = OmniLockConfig::new_dogcoin_from_pubkey( + &Pubkey::from(pubkey_0), + crate::unlock::omni_lock::BTCSignVtype::SegwitP2SH, + ); + let id = Identity::new_dogcoin(H160::from(btc_auth( + &pubkey_3.into(), + crate::unlock::omni_lock::BTCSignVtype::SegwitP2SH, + ))); + cfg.set_admin_config(AdminConfig::new( + H256::default(), + SmtProofEntryVec::default(), + id.clone(), + None, + false, + )); + test_omnilock_rc_input_and_dep(cfg.clone()); + cfg.enable_cobuild(true); + test_omnilock_rc_input_and_dep(cfg.clone()); + + cfg.set_admin_config(AdminConfig::new( + H256::default(), + SmtProofEntryVec::default(), + id, + None, + true, + )); + test_omnilock_rc_input_and_dep(cfg.clone()); + cfg.enable_cobuild(true); + test_omnilock_rc_input_and_dep(cfg); +} + +#[test] +fn test_omnilock_eos_rc_input_and_dep() { + let account0_key = secp256k1::SecretKey::from_slice(ACCOUNT0_KEY.as_bytes()).unwrap(); + let pubkey_0 = secp256k1::PublicKey::from_secret_key(&SECP256K1, &account0_key); + // uncompressed + let mut cfg = OmniLockConfig::new_eos_from_pubkey( + &Pubkey::from(pubkey_0), + crate::unlock::omni_lock::BTCSignVtype::P2PKHUncompressed, + ); + + let account3_key = secp256k1::SecretKey::from_slice(ACCOUNT3_KEY.as_bytes()) + .map_err(|err| format!("invalid sender secret key: {}", err)) + .unwrap(); + let pubkey_3 = secp256k1::PublicKey::from_secret_key(&SECP256K1, &account3_key); + let id = Identity::new_eos(H160::from(eos_auth( + &pubkey_3.into(), + crate::unlock::omni_lock::BTCSignVtype::P2PKHUncompressed, + ))); + + cfg.set_admin_config(AdminConfig::new( + H256::default(), + SmtProofEntryVec::default(), + id.clone(), + None, + false, + )); + test_omnilock_rc_input_and_dep(cfg.clone()); + cfg.enable_cobuild(true); + test_omnilock_rc_input_and_dep(cfg.clone()); + + cfg.set_admin_config(AdminConfig::new( + H256::default(), + SmtProofEntryVec::default(), + id, + None, + true, + )); + test_omnilock_rc_input_and_dep(cfg.clone()); + cfg.enable_cobuild(true); + test_omnilock_rc_input_and_dep(cfg); + + // P2PKHCompressed + let mut cfg = OmniLockConfig::new_eos_from_pubkey( + &Pubkey::from(pubkey_0), + crate::unlock::omni_lock::BTCSignVtype::P2PKHCompressed, + ); + let id = Identity::new_eos(H160::from(eos_auth( + &pubkey_3.into(), + crate::unlock::omni_lock::BTCSignVtype::P2PKHCompressed, + ))); + cfg.set_admin_config(AdminConfig::new( + H256::default(), + SmtProofEntryVec::default(), + id.clone(), + None, + false, + )); + test_omnilock_rc_input_and_dep(cfg.clone()); + cfg.enable_cobuild(true); + test_omnilock_rc_input_and_dep(cfg.clone()); + + cfg.set_admin_config(AdminConfig::new( + H256::default(), + SmtProofEntryVec::default(), + id, + None, + true, + )); + test_omnilock_rc_input_and_dep(cfg.clone()); + cfg.enable_cobuild(true); + test_omnilock_rc_input_and_dep(cfg); +} + +#[test] +fn test_omnilock_tron_rc_input_and_dep() { + let account0_key = secp256k1::SecretKey::from_slice(ACCOUNT0_KEY.as_bytes()).unwrap(); + let pubkey_0 = secp256k1::PublicKey::from_secret_key(&SECP256K1, &account0_key); + + let mut cfg = OmniLockConfig::new_tron_from_pubkey(&Pubkey::from(pubkey_0)); + + let account3_key = secp256k1::SecretKey::from_slice(ACCOUNT3_KEY.as_bytes()) + .map_err(|err| format!("invalid sender secret key: {}", err)) + .unwrap(); + let pubkey_3 = secp256k1::PublicKey::from_secret_key(&SECP256K1, &account3_key); + let id = Identity::new_tron(keccak160(Pubkey::from(pubkey_3).as_bytes())); + + cfg.set_admin_config(AdminConfig::new( + H256::default(), + SmtProofEntryVec::default(), + id.clone(), + None, + false, + )); + test_omnilock_rc_input_and_dep(cfg.clone()); + cfg.enable_cobuild(true); + test_omnilock_rc_input_and_dep(cfg.clone()); + + cfg.set_admin_config(AdminConfig::new( + H256::default(), + SmtProofEntryVec::default(), + id, + None, + true, + )); + test_omnilock_rc_input_and_dep(cfg.clone()); + cfg.enable_cobuild(true); + test_omnilock_rc_input_and_dep(cfg); +} + +#[test] +fn test_omnilock_solana_rc_input_and_dep() { + let account0_key = + ed25519_dalek::SigningKey::from_bytes(ACCOUNT0_KEY.as_bytes().try_into().unwrap()); + let pubkey_0 = account0_key.verifying_key(); + + let mut cfg = OmniLockConfig::new_solana_from_pubkey(&pubkey_0); + + let account3_key = + ed25519_dalek::SigningKey::from_bytes(ACCOUNT3_KEY.as_bytes().try_into().unwrap()); + let pubkey_3 = account3_key.verifying_key(); + let id = Identity::new( + crate::unlock::IdentityFlag::Solana, + blake160(pubkey_3.as_bytes()), + ); + + cfg.set_admin_config(AdminConfig::new( + H256::default(), + SmtProofEntryVec::default(), + id.clone(), + None, + false, + )); + test_omnilock_rc_input_and_dep(cfg.clone()); + cfg.enable_cobuild(true); + test_omnilock_rc_input_and_dep(cfg.clone()); + + cfg.set_admin_config(AdminConfig::new( + H256::default(), + SmtProofEntryVec::default(), + id, + None, + true, + )); + test_omnilock_rc_input_and_dep(cfg.clone()); + cfg.enable_cobuild(true); + test_omnilock_rc_input_and_dep(cfg); +} + +fn test_omnilock_rc_input_and_dep(mut cfg: OmniLockConfig) { + let network_info = NetworkInfo::testnet(); + let unlock_mode = OmniUnlockMode::Admin; + + let receiver = build_sighash_script(ACCOUNT2_ARG); + + let (mut ctx, mut outpoints) = init_context( + vec![(OMNILOCK_BIN, true), (ALWAYS_SUCCESS_BIN, false)], + vec![], + ); + let mut admin_config = cfg.get_admin_config().unwrap().clone(); + let rc_input = admin_config.rce_in_input(); + + let (proof_vec, rc_type_id, rce_cells) = crate::tests::tx_builder::omni_lock_util::generate_rc( + &mut ctx, + admin_config.get_auth().to_smt_key().into(), + rc_input, + ACCOUNT3_ARG, + ); + admin_config.set_proofs(proof_vec); + admin_config.set_rc_type_id(H256::from_slice(rc_type_id.as_ref()).unwrap()); + cfg.set_admin_config(admin_config); + let sender = build_omnilock_script(&cfg); + for (lock, capacity_opt) in [(sender.clone(), Some(300 * ONE_CKB))] { + ctx.add_simple_live_cell(random_out_point(), lock, capacity_opt); + } + + let rc_inputs = if rc_input { + rce_cells + .clone() + .into_iter() + .map(|outpoint| { + let (output, output_data) = ctx.get_input(&outpoint).unwrap(); + LiveCell { + out_point: outpoint, + output, + output_data, + block_number: 0, + tx_index: 0, + } + }) + .collect() + } else { + // rc cell is dep cell + outpoints.extend(rce_cells.clone()); + Vec::new() + }; + let configuration = test_omnilock_config(outpoints); + + let iterator = InputIterator::new_with_cell_collector( + vec![sender.clone()], + Box::new(ctx.to_live_cells_context()) as Box<_>, + ); + let mut builder = SimpleTransactionBuilder::new(configuration, iterator); + + let output = CellOutput::new_builder() + .capacity((110 * ONE_CKB).pack()) + .lock(receiver) + .build(); + builder.add_output_and_data(output.clone(), ckb_types::packed::Bytes::default()); + builder.set_change_lock(sender.clone()); + + if !rc_inputs.is_empty() { + builder.set_rc_cells(rc_inputs) + } + + let account_key = secp256k1::SecretKey::from_slice(ACCOUNT3_KEY.as_bytes()).unwrap(); + let mut sign_context = if cfg.id().flag() == IdentityFlag::Solana { + SignContexts::new_omnilock_solana( + vec![ed25519_dalek::SigningKey::from_bytes( + ACCOUNT3_KEY.as_bytes().try_into().unwrap(), + )], + cfg.clone(), + unlock_mode, + ) + } else { + SignContexts::new_omnilock(vec![account_key], cfg.clone(), unlock_mode) + }; + let hashall_unlock = + crate::transaction::signer::sighash::Secp256k1Blake160SighashAllSignerContext::new(vec![ + account_key, + ]); + sign_context.add_context(Box::new(hashall_unlock)); + + let context = + OmnilockScriptContext::new(cfg.clone(), network_info.url.clone()).unlock_mode(unlock_mode); + let mut contexts = HandlerContexts::default(); + contexts.add_context(Box::new(context) as Box<_>); + + let mut tx_with_groups = builder.build(&contexts).expect("build failed"); + + // let json_tx = ckb_jsonrpc_types::TransactionView::from(tx_with_groups.get_tx_view().clone()); + // println!("tx: {}", serde_json::to_string_pretty(&json_tx).unwrap()); + + TransactionSigner::new(&network_info) + // use unitest lock to verify + .insert_unlocker( + crate::ScriptId::new_data1(H256::from(blake2b_256(OMNILOCK_BIN))), + crate::transaction::signer::omnilock::OmnilockSigner {}, + ) + .sign_transaction(&mut tx_with_groups, &sign_context) + .unwrap(); + let tx = tx_with_groups.get_tx_view().clone(); + let script_groups = tx_with_groups.script_groups.clone(); + + // let json_tx = ckb_jsonrpc_types::TransactionView::from(tx_with_groups.get_tx_view().clone()); + // println!("tx: {}", serde_json::to_string_pretty(&json_tx).unwrap()); + + if rc_input { + assert_eq!(script_groups.len(), 5); + assert_eq!(tx.header_deps().len(), 0); + assert_eq!(tx.cell_deps().len(), 3); // one is omnilock, one is sighash, one is always success + assert_eq!(tx.inputs().len(), 4); + for out_point in tx.input_pts_iter().skip(rce_cells.len()) { + assert_eq!(ctx.get_input(&out_point).unwrap().0.lock(), sender); + } + assert_eq!(tx.outputs().len(), 2); + assert_eq!(tx.output(0).unwrap(), output); + assert_eq!(tx.output(1).unwrap().lock(), sender); + let change_capacity: u64 = tx.output(1).unwrap().capacity().unpack(); + let fee = (300 - 110) * ONE_CKB - change_capacity; + assert_eq!(tx.data().as_reader().serialized_size_in_block() as u64, fee); + } else { + assert_eq!(script_groups.len(), 1); + assert_eq!(tx.header_deps().len(), 0); + assert_eq!(tx.cell_deps().len(), 5); + assert_eq!(tx.inputs().len(), 1); + for out_point in tx.input_pts_iter() { + assert_eq!(ctx.get_input(&out_point).unwrap().0.lock(), sender); + } + assert_eq!(tx.outputs().len(), 2); + assert_eq!(tx.output(0).unwrap(), output); + assert_eq!(tx.output(1).unwrap().lock(), sender); + let change_capacity: u64 = tx.output(1).unwrap().capacity().unpack(); + let fee = (300 - 110) * ONE_CKB - change_capacity; + assert_eq!(tx.data().as_reader().serialized_size_in_block() as u64, fee); + } + + ctx.verify(tx, FEE_RATE).unwrap(); +} + +#[test] +fn test_omnilock_multisign_rc_all() { + test_omnilock_multisign_rc_dep(false, OmniUnlockMode::Admin); + test_omnilock_multisign_rc_dep(false, OmniUnlockMode::Normal); + test_omnilock_multisign_rc_dep(true, OmniUnlockMode::Admin); + test_omnilock_multisign_rc_dep(true, OmniUnlockMode::Normal); +} + +fn test_omnilock_multisign_rc_dep(cobuild: bool, unlock_mode: OmniUnlockMode) { + let network_info = NetworkInfo::testnet(); + let lock_args = vec![ + ACCOUNT0_ARG.clone(), + ACCOUNT1_ARG.clone(), + ACCOUNT2_ARG.clone(), + ]; + let multi_cfg = MultisigConfig::new_with(lock_args, 0, 2).unwrap(); + let mut cfg = OmniLockConfig::new_multisig(multi_cfg); + + let lock_args = vec![ + ACCOUNT3_ARG.clone(), // the different key + ACCOUNT1_ARG.clone(), + ACCOUNT2_ARG.clone(), + ]; + let multi_cfg = MultisigConfig::new_with(lock_args, 0, 2).unwrap(); + let admin_id = Identity::new_multisig(multi_cfg.clone()); + + let (mut ctx, mut outpoints) = init_context(vec![(OMNILOCK_BIN, true)], vec![]); + let (proof_vec, rc_type_id, rce_cells) = crate::tests::tx_builder::omni_lock_util::generate_rc( + &mut ctx, + admin_id.to_smt_key().into(), + false, + ACCOUNT0_ARG, + ); + cfg.set_admin_config(AdminConfig::new( + H256::from_slice(rc_type_id.as_ref()).unwrap(), + proof_vec, + admin_id, + Some(multi_cfg), + false, + )); + cfg.enable_cobuild(cobuild); + + let sender = build_omnilock_script(&cfg); + let receiver = build_sighash_script(ACCOUNT2_ARG); + for (lock, capacity_opt) in [ + (sender.clone(), Some(100 * ONE_CKB)), + (sender.clone(), Some(200 * ONE_CKB)), + (sender.clone(), Some(300 * ONE_CKB)), + ] { + ctx.add_simple_live_cell(random_out_point(), lock, capacity_opt); + } + + outpoints.extend(rce_cells); + + let configuration = test_omnilock_config(outpoints); + + let iterator = InputIterator::new_with_cell_collector( + vec![sender.clone()], + Box::new(ctx.to_live_cells_context()) as Box<_>, + ); + let mut builder = SimpleTransactionBuilder::new(configuration, iterator); + let output = CellOutput::new_builder() + .capacity((120 * ONE_CKB).pack()) + .lock(receiver) + .build(); + builder.add_output_and_data(output.clone(), ckb_types::packed::Bytes::default()); + builder.set_change_lock(sender.clone()); + + let key0 = match unlock_mode { + OmniUnlockMode::Admin => ACCOUNT3_KEY, + OmniUnlockMode::Normal => ACCOUNT0_KEY, + }; + + let sign_context = SignContexts::new_omnilock( + vec![ + secp256k1::SecretKey::from_slice(key0.as_bytes()).unwrap(), + secp256k1::SecretKey::from_slice(ACCOUNT1_KEY.as_bytes()).unwrap(), + ], + cfg.clone(), + unlock_mode, + ); + + let context = + OmnilockScriptContext::new(cfg.clone(), network_info.url.clone()).unlock_mode(unlock_mode); + let mut contexts = HandlerContexts::default(); + contexts.add_context(Box::new(context) as Box<_>); + + let mut tx_with_groups = builder.build(&contexts).expect("build failed"); + + TransactionSigner::new(&network_info) + // use unitest lock to verify + .insert_unlocker( + crate::ScriptId::new_data1(H256::from(blake2b_256(OMNILOCK_BIN))), + crate::transaction::signer::omnilock::OmnilockSigner {}, + ) + .sign_transaction(&mut tx_with_groups, &sign_context) + .unwrap(); + let tx = tx_with_groups.get_tx_view().clone(); + let script_groups = tx_with_groups.script_groups.clone(); + + assert_eq!(script_groups.len(), 1); + assert_eq!(tx.header_deps().len(), 0); + assert_eq!(tx.cell_deps().len(), 4); + assert_eq!(tx.inputs().len(), 2); + for out_point in tx.input_pts_iter() { + assert_eq!(ctx.get_input(&out_point).unwrap().0.lock(), sender); + } + assert_eq!(tx.outputs().len(), 2); + assert_eq!(tx.output(0).unwrap(), output); + assert_eq!(tx.output(1).unwrap().lock(), sender); + let change_capacity: u64 = tx.output(1).unwrap().capacity().unpack(); + let fee = (300 - 120) * ONE_CKB - change_capacity; + assert_eq!(tx.data().as_reader().serialized_size_in_block() as u64, fee); + + ctx.verify(tx, FEE_RATE).unwrap(); +} + +#[test] +fn test_omnilock_owner_lock_rc_dep_all() { + test_omnilock_owner_lock_rc_dep(false); + test_omnilock_owner_lock_rc_dep(true); +} + +fn test_omnilock_owner_lock_rc_dep(cobuild: bool) { + let network_info = NetworkInfo::testnet(); + let unlock_mode = OmniUnlockMode::Admin; + let receiver = build_sighash_script(ACCOUNT2_ARG); + let sender1 = build_sighash_script(ACCOUNT1_ARG); + let hash = H160::from_slice(&sender1.calc_script_hash().as_slice()[0..20]).unwrap(); + let mut cfg = OmniLockConfig::new_ownerlock(hash); + + let owner_sender = build_sighash_script(ACCOUNT3_ARG); + let (mut ctx, mut outpoints) = init_context( + vec![(OMNILOCK_BIN, true)], + vec![(owner_sender.clone(), Some(61 * ONE_CKB))], + ); + + let owner_hash = H160::from_slice(&owner_sender.calc_script_hash().as_slice()[0..20]).unwrap(); + let owner_id = Identity::new_ownerlock(owner_hash); + let (proof_vec, rc_type_id, rce_cells) = crate::tests::tx_builder::omni_lock_util::generate_rc( + &mut ctx, + owner_id.to_smt_key().into(), + false, + ACCOUNT0_ARG, + ); + cfg.set_admin_config(AdminConfig::new( + H256::from_slice(rc_type_id.as_ref()).unwrap(), + proof_vec, + owner_id, + None, + false, + )); + cfg.enable_cobuild(cobuild); + let sender0 = build_omnilock_script(&cfg); + for (lock, capacity_opt) in [(sender0.clone(), Some(150 * ONE_CKB))] { + ctx.add_simple_live_cell(random_out_point(), lock, capacity_opt); + } + + outpoints.extend(rce_cells); + + let configuration = test_omnilock_config(outpoints); + + let iterator = InputIterator::new_with_cell_collector( + vec![sender0.clone(), owner_sender.clone()], + Box::new(ctx.to_live_cells_context()) as Box<_>, + ); + let mut builder = SimpleTransactionBuilder::new(configuration, iterator); + let output = CellOutput::new_builder() + .capacity((110 * ONE_CKB).pack()) + .lock(receiver) + .build(); + builder.add_output_and_data(output.clone(), ckb_types::packed::Bytes::default()); + builder.set_change_lock(sender0.clone()); + + let mut sign_context = SignContexts::new_omnilock( + vec![secp256k1::SecretKey::from_slice(ACCOUNT0_KEY.as_bytes()).unwrap()], + cfg.clone(), + unlock_mode, + ); + let hashall_unlock = + crate::transaction::signer::sighash::Secp256k1Blake160SighashAllSignerContext::new(vec![ + secp256k1::SecretKey::from_slice(ACCOUNT3_KEY.as_bytes()).unwrap(), + ]); + sign_context.add_context(Box::new(hashall_unlock)); + + let context = + OmnilockScriptContext::new(cfg.clone(), network_info.url.clone()).unlock_mode(unlock_mode); + let mut contexts = HandlerContexts::default(); + contexts.add_context(Box::new(context) as Box<_>); + + let mut tx_with_groups = builder.build(&contexts).expect("build failed"); + + TransactionSigner::new(&network_info) + // use unitest lock to verify + .insert_unlocker( + crate::ScriptId::new_data1(H256::from(blake2b_256(OMNILOCK_BIN))), + crate::transaction::signer::omnilock::OmnilockSigner {}, + ) + .sign_transaction(&mut tx_with_groups, &sign_context) + .unwrap(); + let tx = tx_with_groups.get_tx_view().clone(); + let script_groups = tx_with_groups.script_groups.clone(); + + assert_eq!(script_groups.len(), 2); + assert_eq!(tx.header_deps().len(), 0); + assert_eq!(tx.cell_deps().len(), 5); + assert_eq!(tx.inputs().len(), 2); + let mut senders = vec![sender0.clone(), owner_sender.clone()]; + for out_point in tx.input_pts_iter() { + let sender = ctx.get_input(&out_point).unwrap().0.lock(); + assert!(senders.contains(&sender)); + senders.retain(|x| x != &sender); + } + assert_eq!(tx.outputs().len(), 2); + assert_eq!(tx.output(0).unwrap(), output); + assert_eq!(tx.output(1).unwrap().lock(), sender0); + let change_capacity: u64 = tx.output(1).unwrap().capacity().unpack(); + let fee = (150 + 61 - 110) * ONE_CKB - change_capacity; + assert_eq!(tx.data().as_reader().serialized_size_in_block() as u64, fee); + ctx.verify(tx, FEE_RATE).unwrap(); +} + +#[test] +fn test_omnilock_transfer_acp() { + test_omnilock_transfer_from_acp(false); + test_omnilock_transfer_from_acp(true); + test_omnilock_transfer_to_acp(false); + test_omnilock_transfer_to_acp(true); +} + +fn test_omnilock_transfer_from_acp(cobuild: bool) { + // account0 sender with acp + // account2 receiver + let unlock_mode = OmniUnlockMode::Normal; + let network_info = NetworkInfo::testnet(); + + let sender_key = secp256k1::SecretKey::from_slice(ACCOUNT0_KEY.as_bytes()) + .map_err(|err| format!("invalid sender secret key: {}", err)) + .unwrap(); + let pubkey = secp256k1::PublicKey::from_secret_key(&SECP256K1, &sender_key); + + let pubkey_hash = blake160(&pubkey.serialize()); + let mut cfg = OmniLockConfig::new_pubkey_hash(pubkey_hash); + + cfg.set_acp_config(OmniLockAcpConfig::new(0, 0)); + cfg.enable_cobuild(cobuild); + + let sender = build_omnilock_script(&cfg); + let receiver = build_sighash_script(ACCOUNT2_ARG); + + let (ctx, outpoints) = init_context( + vec![(OMNILOCK_BIN, true)], + vec![(sender.clone(), Some(300 * ONE_CKB))], + ); + + let configuration = test_omnilock_config(outpoints); + + let iterator = InputIterator::new_with_cell_collector( + vec![sender.clone()], + Box::new(ctx.to_live_cells_context()) as Box<_>, + ); + let mut builder = SimpleTransactionBuilder::new(configuration, iterator); + + let output = CellOutput::new_builder() + .capacity((110 * ONE_CKB).pack()) + .lock(receiver) + .build(); + + builder.add_output_and_data(output.clone(), ckb_types::packed::Bytes::default()); + builder.set_change_lock(sender.clone()); + + let sign_context = SignContexts::new_omnilock(vec![sender_key], cfg.clone(), unlock_mode); + + let context = + OmnilockScriptContext::new(cfg.clone(), network_info.url.clone()).unlock_mode(unlock_mode); + let mut contexts = HandlerContexts::default(); + contexts.add_context(Box::new(context) as Box<_>); + + let mut tx_with_groups = builder.build(&contexts).expect("build failed"); + + // let json_tx = ckb_jsonrpc_types::TransactionView::from(tx_with_groups.get_tx_view().clone()); + // println!("tx: {}", serde_json::to_string_pretty(&json_tx).unwrap()); + + TransactionSigner::new(&network_info) + // use unitest lock to verify + .insert_unlocker( + crate::ScriptId::new_data1(H256::from(blake2b_256(OMNILOCK_BIN))), + crate::transaction::signer::omnilock::OmnilockSigner {}, + ) + .sign_transaction(&mut tx_with_groups, &sign_context) + .unwrap(); + let tx = tx_with_groups.get_tx_view().clone(); + let script_groups = tx_with_groups.script_groups.clone(); + + // let json_tx = ckb_jsonrpc_types::TransactionView::from(tx_with_groups.get_tx_view().clone()); + // println!("tx: {}", serde_json::to_string_pretty(&json_tx).unwrap()); + + assert_eq!(script_groups.len(), 1); + assert_eq!(tx.header_deps().len(), 0); + assert_eq!(tx.cell_deps().len(), 1); + assert_eq!(tx.inputs().len(), 1); + for out_point in tx.input_pts_iter() { + assert_eq!(ctx.get_input(&out_point).unwrap().0.lock(), sender); + } + assert_eq!(tx.outputs().len(), 2); + assert_eq!(tx.output(0).unwrap(), output); + assert_eq!(tx.output(1).unwrap().lock(), sender); + let change_capacity: u64 = tx.output(1).unwrap().capacity().unpack(); + let fee = (300 - 110) * ONE_CKB - change_capacity; + assert_eq!(tx.data().as_reader().serialized_size_in_block() as u64, fee); + + ctx.verify(tx, FEE_RATE).unwrap(); +} + +fn test_omnilock_transfer_to_acp(cobuild: bool) { + // account0 sender + // account2 receiver with acp + let unlock_mode = OmniUnlockMode::Normal; + let network_info = NetworkInfo::testnet(); + + let sender = build_sighash_script(ACCOUNT0_ARG); + let receiver_key = secp256k1::SecretKey::from_slice(ACCOUNT2_KEY.as_bytes()) + .map_err(|err| format!("invalid sender secret key: {}", err)) + .unwrap(); + let pubkey = secp256k1::PublicKey::from_secret_key(&SECP256K1, &receiver_key); + + let pubkey_hash = blake160(&pubkey.serialize()); + let mut cfg = OmniLockConfig::new_pubkey_hash(pubkey_hash); + cfg.set_acp_config(OmniLockAcpConfig::new(9, 5)); + cfg.enable_cobuild(cobuild); + let receiver = build_omnilock_script(&cfg); + + let (ctx, outpoints) = init_context( + vec![(OMNILOCK_BIN, true)], + vec![ + (sender.clone(), Some(100 * ONE_CKB)), + (receiver.clone(), Some(61 * ONE_CKB)), + ], + ); + + let configuration = test_omnilock_config(outpoints); + + let iterator = InputIterator::new_with_cell_collector( + vec![sender.clone(), receiver.clone()], + Box::new(ctx.to_live_cells_context()) as Box<_>, + ); + let mut builder = SimpleTransactionBuilder::new(configuration, iterator); + + let output = CellOutput::new_builder() + .capacity(((61 + 10) * ONE_CKB).pack()) + .lock(receiver) + .build(); + builder.add_output_and_data(output.clone(), ckb_types::packed::Bytes::default()); + builder.set_change_lock(sender.clone()); + + let mut sign_context = SignContexts::new_omnilock(vec![receiver_key], cfg.clone(), unlock_mode); + let hashall_unlock = + crate::transaction::signer::sighash::Secp256k1Blake160SighashAllSignerContext::new(vec![ + secp256k1::SecretKey::from_slice(ACCOUNT0_KEY.as_bytes()).unwrap(), + ]); + sign_context.add_context(Box::new(hashall_unlock)); + + let context = OmnilockScriptContext::new(cfg.clone(), network_info.url.clone()); + let mut contexts = HandlerContexts::default(); + contexts.add_context(Box::new(context) as Box<_>); + + let mut tx_with_groups = builder.build(&contexts).expect("build failed"); + + TransactionSigner::new(&network_info) + .insert_unlocker( + crate::ScriptId::new_data1(H256::from(blake2b_256(OMNILOCK_BIN))), + crate::transaction::signer::omnilock::OmnilockSigner {}, + ) + .sign_transaction(&mut tx_with_groups, &sign_context) + .unwrap(); + + let tx = tx_with_groups.get_tx_view().clone(); + let script_groups = tx_with_groups.script_groups.clone(); + + assert_eq!(script_groups.len(), 2); + assert_eq!(tx.header_deps().len(), 0); + assert_eq!(tx.cell_deps().len(), 2); + assert_eq!(tx.inputs().len(), 2); + assert_eq!(tx.outputs().len(), 2); + assert_eq!(tx.output(0).unwrap(), output); + assert_eq!(tx.output(1).unwrap().lock(), sender); + + let change_capacity: u64 = tx.output(1).unwrap().capacity().unpack(); + let fee = (161 - 71) * ONE_CKB - change_capacity; + assert_eq!(tx.data().as_reader().serialized_size_in_block() as u64, fee); + + ctx.verify(tx, FEE_RATE).unwrap(); +} diff --git a/src/tests/transaction/sighash.rs b/src/tests/transaction/sighash.rs index a2c6ca65..17654a84 100644 --- a/src/tests/transaction/sighash.rs +++ b/src/tests/transaction/sighash.rs @@ -18,7 +18,7 @@ use crate::{ fn test_transfer_from_sighash() { let sender = build_sighash_script(ACCOUNT1_ARG); let receiver = build_sighash_script(ACCOUNT2_ARG); - let ctx = init_context( + let (ctx, _) = init_context( Vec::new(), vec![ (sender.clone(), Some(100 * ONE_CKB)), diff --git a/src/tests/transaction/typeid.rs b/src/tests/transaction/typeid.rs index d29cc9aa..98109f85 100644 --- a/src/tests/transaction/typeid.rs +++ b/src/tests/transaction/typeid.rs @@ -15,7 +15,7 @@ use crate::{ #[test] fn test_deploy_id() { let sender = build_sighash_script(ACCOUNT1_ARG); - let ctx = init_context(Vec::new(), vec![(sender.clone(), Some(10_0000 * ONE_CKB))]); + let (ctx, _) = init_context(Vec::new(), vec![(sender.clone(), Some(10_0000 * ONE_CKB))]); let network_info = NetworkInfo::testnet(); let type_script = diff --git a/src/tests/tx_builder/acp.rs b/src/tests/tx_builder/acp.rs new file mode 100644 index 00000000..b3cc42c0 --- /dev/null +++ b/src/tests/tx_builder/acp.rs @@ -0,0 +1,106 @@ +use std::collections::HashMap; + +use ckb_hash::blake2b_256; + +use ckb_types::{ + bytes::Bytes, + core::ScriptHashType, + packed::{CellOutput, Script, WitnessArgs}, + prelude::*, + H256, +}; + +use crate::constants::{ONE_CKB, SIGHASH_TYPE_HASH}; +use crate::tests::{ + build_sighash_script, init_context, ACCOUNT1_ARG, ACCOUNT1_KEY, ACCOUNT2_ARG, ACP_BIN, FEE_RATE, +}; +use crate::traits::SecpCkbRawKeySigner; +use crate::tx_builder::{ + acp::{AcpTransferBuilder, AcpTransferReceiver}, + CapacityBalancer, TxBuilder, +}; +use crate::unlock::{AcpUnlocker, ScriptUnlocker}; +use crate::ScriptId; + +#[test] +fn test_transfer_to_acp() { + let data_hash = H256::from(blake2b_256(ACP_BIN)); + let sender = build_sighash_script(ACCOUNT1_ARG); + let receiver = Script::new_builder() + .code_hash(data_hash.pack()) + .hash_type(ScriptHashType::Data1.into()) + .args(Bytes::from(ACCOUNT2_ARG.0.to_vec()).pack()) + .build(); + let (ctx, _) = init_context( + vec![(ACP_BIN, true)], + vec![ + (sender.clone(), Some(100 * ONE_CKB)), + (sender.clone(), Some(200 * ONE_CKB)), + (sender.clone(), Some(300 * ONE_CKB)), + (receiver.clone(), Some(99 * ONE_CKB)), + ], + ); + + let acp_receiver = AcpTransferReceiver::new(receiver.clone(), 150 * ONE_CKB); + let builder = AcpTransferBuilder::new(vec![acp_receiver]); + let placeholder_witness = WitnessArgs::new_builder() + .lock(Some(Bytes::from(vec![0u8; 65])).pack()) + .build(); + let balancer = + CapacityBalancer::new_simple(sender.clone(), placeholder_witness.clone(), FEE_RATE); + + let account1_key = secp256k1::SecretKey::from_slice(ACCOUNT1_KEY.as_bytes()).unwrap(); + let signer1 = SecpCkbRawKeySigner::new_with_secret_keys(vec![account1_key]); + let sighash_unlocker = AcpUnlocker::from(Box::new(signer1) as Box<_>); + let acp_unlocker = AcpUnlocker::from(Box::::default() as Box<_>); + let mut unlockers: HashMap> = HashMap::default(); + unlockers.insert( + ScriptId::new_type(SIGHASH_TYPE_HASH), + Box::new(sighash_unlocker), + ); + unlockers.insert(ScriptId::new_data1(data_hash), Box::new(acp_unlocker)); + + let mut cell_collector = ctx.to_live_cells_context(); + let (tx, locked_groups) = builder + .build_unlocked(&mut cell_collector, &ctx, &ctx, &ctx, &balancer, &unlockers) + .unwrap(); + + assert!(locked_groups.is_empty()); + assert_eq!(tx.header_deps().len(), 0); + assert_eq!(tx.cell_deps().len(), 2); + assert_eq!(tx.inputs().len(), 3); + let input_cells = vec![ + CellOutput::new_builder() + .capacity((99 * ONE_CKB).pack()) + .lock(receiver.clone()) + .build(), + CellOutput::new_builder() + .capacity((100 * ONE_CKB).pack()) + .lock(sender.clone()) + .build(), + CellOutput::new_builder() + .capacity((200 * ONE_CKB).pack()) + .lock(sender.clone()) + .build(), + ]; + for (idx, out_point) in tx.input_pts_iter().enumerate() { + assert_eq!(ctx.get_input(&out_point).unwrap().0, input_cells[idx]); + } + assert_eq!(tx.outputs().len(), 2); + let acp_output = CellOutput::new_builder() + .capacity(((99 + 150) * ONE_CKB).pack()) + .lock(receiver) + .build(); + assert_eq!(tx.output(0).unwrap(), acp_output); + assert_eq!(tx.output(1).unwrap().lock(), sender); + let witnesses = tx + .witnesses() + .into_iter() + .map(|w| w.raw_data()) + .collect::>(); + assert_eq!(witnesses.len(), 3); + assert_eq!(witnesses[0].len(), 0); + assert_eq!(witnesses[1].len(), placeholder_witness.as_slice().len()); + assert_eq!(witnesses[2].len(), 0); + ctx.verify(tx, FEE_RATE).unwrap(); +} diff --git a/src/tests/tx_builder/cheque.rs b/src/tests/tx_builder/cheque.rs new file mode 100644 index 00000000..3bfa2854 --- /dev/null +++ b/src/tests/tx_builder/cheque.rs @@ -0,0 +1,239 @@ +use std::collections::HashMap; + +use ckb_hash::blake2b_256; +use ckb_types::{ + bytes::Bytes, + core::ScriptHashType, + packed::{CellInput, CellOutput, Script, WitnessArgs}, + prelude::*, + H256, +}; + +use crate::constants::{CHEQUE_CELL_SINCE, ONE_CKB, SIGHASH_TYPE_HASH}; +use crate::tests::{ + build_cheque_script, build_sighash_script, init_context, ACCOUNT1_ARG, ACCOUNT1_KEY, + ACCOUNT2_ARG, ACCOUNT2_KEY, CHEQUE_BIN, FEE_RATE, SUDT_BIN, +}; +use crate::traits::SecpCkbRawKeySigner; +use crate::tx_builder::{ + cheque::{ChequeClaimBuilder, ChequeWithdrawBuilder}, + CapacityBalancer, TxBuilder, +}; +use crate::unlock::{ChequeAction, ChequeUnlocker, ScriptUnlocker, SecpSighashUnlocker}; +use crate::ScriptId; + +use crate::test_util::random_out_point; + +#[test] +fn test_cheque_claim() { + let sudt_data_hash = H256::from(blake2b_256(SUDT_BIN)); + let cheque_data_hash = H256::from(blake2b_256(CHEQUE_BIN)); + let sender = build_sighash_script(ACCOUNT1_ARG); + let receiver = build_sighash_script(ACCOUNT2_ARG); + let cheque_script = build_cheque_script(&sender, &receiver, cheque_data_hash.clone()); + let type_script = Script::new_builder() + .code_hash(sudt_data_hash.pack()) + .hash_type(ScriptHashType::Data1.into()) + .args(Bytes::from(vec![9u8; 32]).pack()) + .build(); + let (mut ctx, _) = init_context( + vec![(CHEQUE_BIN, true), (SUDT_BIN, false)], + vec![ + (receiver.clone(), Some(100 * ONE_CKB)), + (receiver.clone(), Some(200 * ONE_CKB)), + ], + ); + + let receiver_input = CellInput::new(random_out_point(), 0); + let receiver_output = CellOutput::new_builder() + .capacity((200 * ONE_CKB).pack()) + .lock(receiver.clone()) + .type_(Some(type_script.clone()).pack()) + .build(); + let receiver_data = Bytes::from(1000u128.to_le_bytes().to_vec()); + ctx.add_live_cell( + receiver_input.clone(), + receiver_output.clone(), + receiver_data, + None, + ); + + let cheque_input = CellInput::new(random_out_point(), 0); + let cheque_output = CellOutput::new_builder() + .capacity((220 * ONE_CKB).pack()) + .lock(cheque_script) + .type_(Some(type_script).pack()) + .build(); + let cheque_data = Bytes::from(500u128.to_le_bytes().to_vec()); + ctx.add_live_cell( + cheque_input.clone(), + cheque_output.clone(), + cheque_data, + None, + ); + + let builder = ChequeClaimBuilder::new(vec![cheque_input], receiver_input, sender.clone()); + let placeholder_witness = WitnessArgs::new_builder() + .lock(Some(Bytes::from(vec![0u8; 65])).pack()) + .build(); + let balancer = + CapacityBalancer::new_simple(receiver.clone(), placeholder_witness.clone(), FEE_RATE); + + let account2_key = secp256k1::SecretKey::from_slice(ACCOUNT2_KEY.as_bytes()).unwrap(); + let signer = SecpCkbRawKeySigner::new_with_secret_keys(vec![account2_key]); + let sighash_unlocker = SecpSighashUnlocker::from(Box::new(signer.clone()) as Box<_>); + let cheque_unlocker = ChequeUnlocker::from((Box::new(signer) as Box<_>, ChequeAction::Claim)); + let mut unlockers: HashMap> = HashMap::default(); + unlockers.insert( + ScriptId::new_type(SIGHASH_TYPE_HASH), + Box::new(sighash_unlocker), + ); + unlockers.insert( + ScriptId::new_data1(cheque_data_hash), + Box::new(cheque_unlocker), + ); + + let mut cell_collector = ctx.to_live_cells_context(); + let (tx, locked_groups) = builder + .build_unlocked(&mut cell_collector, &ctx, &ctx, &ctx, &balancer, &unlockers) + .unwrap(); + + assert!(locked_groups.is_empty()); + assert_eq!(tx.header_deps().len(), 0); + assert_eq!(tx.cell_deps().len(), 3); + assert_eq!(tx.inputs().len(), 3); + let input_cells = vec![ + cheque_output, + receiver_output.clone(), + CellOutput::new_builder() + .capacity((100 * ONE_CKB).pack()) + .lock(receiver.clone()) + .build(), + ]; + for (idx, out_point) in tx.input_pts_iter().enumerate() { + assert_eq!(ctx.get_input(&out_point).unwrap().0, input_cells[idx]); + } + assert_eq!(tx.outputs().len(), 3); + assert_eq!(tx.output(0).unwrap(), receiver_output); + let sender_output = CellOutput::new_builder() + .capacity((220 * ONE_CKB).pack()) + .lock(sender) + .build(); + assert_eq!(tx.output(1).unwrap(), sender_output); + assert_eq!(tx.output(2).unwrap().lock(), receiver); + let expected_outputs_data = vec![ + Bytes::from((1000u128 + 500u128).to_le_bytes().to_vec()), + Bytes::default(), + Bytes::default(), + ]; + let outputs_data = tx + .outputs_data() + .into_iter() + .map(|d| d.raw_data()) + .collect::>(); + assert_eq!(outputs_data, expected_outputs_data); + let witnesses = tx + .witnesses() + .into_iter() + .map(|w| w.raw_data()) + .collect::>(); + assert_eq!(witnesses.len(), 3); + assert_eq!(witnesses[0].len(), 0); + assert_eq!(witnesses[1].len(), placeholder_witness.as_slice().len()); + assert_eq!(witnesses[2].len(), 0); + ctx.verify(tx, FEE_RATE).unwrap(); +} + +#[test] +fn test_cheque_withdraw() { + let sudt_data_hash = H256::from(blake2b_256(SUDT_BIN)); + let cheque_data_hash = H256::from(blake2b_256(CHEQUE_BIN)); + let sender = build_sighash_script(ACCOUNT1_ARG); + let receiver = build_sighash_script(ACCOUNT2_ARG); + let cheque_script = build_cheque_script(&sender, &receiver, cheque_data_hash.clone()); + let type_script = Script::new_builder() + .code_hash(sudt_data_hash.pack()) + .hash_type(ScriptHashType::Data1.into()) + .args(Bytes::from(vec![9u8; 32]).pack()) + .build(); + let (mut ctx, _) = init_context( + vec![(CHEQUE_BIN, true), (SUDT_BIN, false)], + vec![ + (sender.clone(), Some(100 * ONE_CKB)), + (sender.clone(), Some(200 * ONE_CKB)), + ], + ); + + let cheque_out_point = random_out_point(); + let cheque_input = CellInput::new(cheque_out_point.clone(), CHEQUE_CELL_SINCE); + let cheque_output = CellOutput::new_builder() + .capacity((220 * ONE_CKB).pack()) + .lock(cheque_script) + .type_(Some(type_script).pack()) + .build(); + let cheque_data = Bytes::from(500u128.to_le_bytes().to_vec()); + ctx.add_live_cell(cheque_input, cheque_output.clone(), cheque_data, None); + + let builder = ChequeWithdrawBuilder::new(vec![cheque_out_point], sender.clone(), None); + let placeholder_witness = WitnessArgs::new_builder() + .lock(Some(Bytes::from(vec![0u8; 65])).pack()) + .build(); + let balancer = + CapacityBalancer::new_simple(sender.clone(), placeholder_witness.clone(), FEE_RATE); + + let account1_key = secp256k1::SecretKey::from_slice(ACCOUNT1_KEY.as_bytes()).unwrap(); + let signer = SecpCkbRawKeySigner::new_with_secret_keys(vec![account1_key]); + let sighash_unlocker = SecpSighashUnlocker::from(Box::new(signer.clone()) as Box<_>); + let cheque_unlocker = + ChequeUnlocker::from((Box::new(signer) as Box<_>, ChequeAction::Withdraw)); + let mut unlockers: HashMap> = HashMap::default(); + unlockers.insert( + ScriptId::new_type(SIGHASH_TYPE_HASH), + Box::new(sighash_unlocker), + ); + unlockers.insert( + ScriptId::new_data1(cheque_data_hash), + Box::new(cheque_unlocker), + ); + + let mut cell_collector = ctx.to_live_cells_context(); + let (tx, locked_groups) = builder + .build_unlocked(&mut cell_collector, &ctx, &ctx, &ctx, &balancer, &unlockers) + .unwrap(); + + assert!(locked_groups.is_empty()); + assert_eq!(tx.header_deps().len(), 0); + assert_eq!(tx.cell_deps().len(), 3); + assert_eq!(tx.inputs().len(), 2); + let input_cells = vec![ + cheque_output.clone(), + CellOutput::new_builder() + .capacity((100 * ONE_CKB).pack()) + .lock(sender.clone()) + .build(), + ]; + for (idx, out_point) in tx.input_pts_iter().enumerate() { + assert_eq!(ctx.get_input(&out_point).unwrap().0, input_cells[idx]); + } + assert_eq!(tx.outputs().len(), 2); + let sender_output = cheque_output.as_builder().lock(sender.clone()).build(); + assert_eq!(tx.output(0).unwrap(), sender_output); + assert_eq!(tx.output(1).unwrap().lock(), sender); + let expected_outputs_data = vec![ + Bytes::from(500u128.to_le_bytes().to_vec()), + Bytes::default(), + ]; + let outputs_data = tx + .outputs_data() + .into_iter() + .map(|d| d.raw_data()) + .collect::>(); + assert_eq!(outputs_data, expected_outputs_data); + let witnesses_len = tx + .witnesses() + .into_iter() + .map(|w| w.raw_data().len()) + .collect::>(); + assert_eq!(witnesses_len, vec![0, placeholder_witness.as_slice().len()]); + ctx.verify(tx, FEE_RATE).unwrap(); +} diff --git a/src/tests/cycle.rs b/src/tests/tx_builder/cycle.rs similarity index 89% rename from src/tests/cycle.rs rename to src/tests/tx_builder/cycle.rs index 921c3f9f..dd67eb18 100644 --- a/src/tests/cycle.rs +++ b/src/tests/tx_builder/cycle.rs @@ -22,7 +22,7 @@ use crate::{ ScriptGroup, ScriptId, }; -const CYCLE_BIN: &[u8] = include_bytes!("../test-data/cycle"); +const CYCLE_BIN: &[u8] = include_bytes!("../../../test-data/cycle"); pub struct CycleUnlocker { loops: u64, @@ -99,10 +99,13 @@ fn test_change_enough(loops: u64) { let sender = build_script(loops); let receiver = build_sighash_script(ACCOUNT2_ARG); - let ctx: &'static Context = Box::leak(Box::new(init_context( - vec![(CYCLE_BIN, true)], - vec![(sender.clone(), Some(200 * ONE_CKB))], - ))); + let ctx: &'static Context = Box::leak(Box::new( + init_context( + vec![(CYCLE_BIN, true)], + vec![(sender.clone(), Some(200 * ONE_CKB))], + ) + .0, + )); let output = CellOutput::new_builder() .capacity((140 * ONE_CKB).pack()) @@ -145,10 +148,13 @@ fn vsize_big_and_fee_enough() { let sender = build_script(loops); let receiver = build_sighash_script(ACCOUNT2_ARG); - let ctx: &'static Context = Box::leak(Box::new(init_context( - vec![(CYCLE_BIN, true)], - vec![(sender.clone(), Some(200 * ONE_CKB + 123_456))], - ))); + let ctx: &'static Context = Box::leak(Box::new( + init_context( + vec![(CYCLE_BIN, true)], + vec![(sender.clone(), Some(200 * ONE_CKB + 123_456))], + ) + .0, + )); let output = CellOutput::new_builder() .capacity((200 * ONE_CKB).pack()) @@ -191,10 +197,13 @@ fn vsize_big_and_fee_not_enough() { let sender = build_script(loops); let receiver = build_sighash_script(ACCOUNT2_ARG); - let ctx: &'static Context = Box::leak(Box::new(init_context( - vec![(CYCLE_BIN, true)], - vec![(sender.clone(), Some(200 * ONE_CKB + 456))], - ))); + let ctx: &'static Context = Box::leak(Box::new( + init_context( + vec![(CYCLE_BIN, true)], + vec![(sender.clone(), Some(200 * ONE_CKB + 456))], + ) + .0, + )); let output = CellOutput::new_builder() .capacity((200 * ONE_CKB).pack()) @@ -224,13 +233,16 @@ fn vsize_big_and_can_find_more_capacity() { let sender = build_script(loops); let receiver = build_sighash_script(ACCOUNT2_ARG); - let ctx: &'static Context = Box::leak(Box::new(init_context( - vec![(CYCLE_BIN, true)], - vec![ - (sender.clone(), Some(200 * ONE_CKB + 286)), // 286 is fee calculated from tx_size - (sender.clone(), Some(70 * ONE_CKB)), - ], - ))); + let ctx: &'static Context = Box::leak(Box::new( + init_context( + vec![(CYCLE_BIN, true)], + vec![ + (sender.clone(), Some(200 * ONE_CKB + 286)), // 286 is fee calculated from tx_size + (sender.clone(), Some(70 * ONE_CKB)), + ], + ) + .0, + )); let output = CellOutput::new_builder() .capacity((200 * ONE_CKB).pack()) @@ -314,13 +326,16 @@ fn vsize_big_and_cannot_find_more_capacity() { let sender = build_script(loops); let receiver = build_sighash_script(ACCOUNT2_ARG); - let ctx: &'static Context = Box::leak(Box::new(init_context( - vec![(CYCLE_BIN, true)], - vec![ - (sender.clone(), Some(200 * ONE_CKB + 286)), // 286 is fee calculated from tx_size - (sender.clone(), Some(49 * ONE_CKB)), - ], - ))); + let ctx: &'static Context = Box::leak(Box::new( + init_context( + vec![(CYCLE_BIN, true)], + vec![ + (sender.clone(), Some(200 * ONE_CKB + 286)), // 286 is fee calculated from tx_size + (sender.clone(), Some(49 * ONE_CKB)), + ], + ) + .0, + )); let output = CellOutput::new_builder() .capacity((200 * ONE_CKB).pack()) diff --git a/src/tests/tx_builder/dao.rs b/src/tests/tx_builder/dao.rs new file mode 100644 index 00000000..353b4322 --- /dev/null +++ b/src/tests/tx_builder/dao.rs @@ -0,0 +1,320 @@ +use std::collections::HashMap; + +use ckb_dao_utils::pack_dao_data; + +use ckb_types::{ + bytes::Bytes, + core::{Capacity, EpochNumberWithFraction, HeaderBuilder}, + packed::{CellInput, CellOutput, ScriptOpt, WitnessArgs}, + prelude::*, +}; + +use crate::constants::{ONE_CKB, SIGHASH_TYPE_HASH}; +use crate::tests::{ + build_dao_script, build_sighash_script, init_context, ACCOUNT1_ARG, ACCOUNT1_KEY, FEE_RATE, +}; +use crate::traits::SecpCkbRawKeySigner; +use crate::tx_builder::{ + dao::{ + DaoDepositBuilder, DaoDepositReceiver, DaoPrepareBuilder, DaoWithdrawBuilder, + DaoWithdrawItem, DaoWithdrawReceiver, + }, + CapacityBalancer, TxBuilder, +}; +use crate::unlock::{ScriptUnlocker, SecpSighashUnlocker}; +use crate::util::{calculate_dao_maximum_withdraw4, minimal_unlock_point}; +use crate::{ScriptId, Since, SinceType}; + +use crate::test_util::random_out_point; + +#[test] +fn test_dao_deposit() { + let sender = build_sighash_script(ACCOUNT1_ARG); + let (ctx, _) = init_context( + Vec::new(), + vec![ + (sender.clone(), Some(100 * ONE_CKB)), + (sender.clone(), Some(200 * ONE_CKB)), + (sender.clone(), Some(300 * ONE_CKB)), + ], + ); + + let deposit_receiver = DaoDepositReceiver::new(sender.clone(), 120 * ONE_CKB); + let builder = DaoDepositBuilder::new(vec![deposit_receiver]); + let placeholder_witness = WitnessArgs::new_builder() + .lock(Some(Bytes::from(vec![0u8; 65])).pack()) + .build(); + let balancer = + CapacityBalancer::new_simple(sender.clone(), placeholder_witness.clone(), FEE_RATE); + + let account1_key = secp256k1::SecretKey::from_slice(ACCOUNT1_KEY.as_bytes()).unwrap(); + let signer = SecpCkbRawKeySigner::new_with_secret_keys(vec![account1_key]); + let script_unlocker = SecpSighashUnlocker::from(Box::new(signer) as Box<_>); + let mut unlockers: HashMap> = HashMap::default(); + unlockers.insert( + ScriptId::new_type(SIGHASH_TYPE_HASH.clone()), + Box::new(script_unlocker), + ); + + let mut cell_collector = ctx.to_live_cells_context(); + let (tx, locked_groups) = builder + .build_unlocked(&mut cell_collector, &ctx, &ctx, &ctx, &balancer, &unlockers) + .unwrap(); + + assert!(locked_groups.is_empty()); + assert_eq!(tx.header_deps().len(), 0); + assert_eq!(tx.cell_deps().len(), 2); + assert_eq!(tx.inputs().len(), 2); + for out_point in tx.input_pts_iter() { + assert_eq!(ctx.get_input(&out_point).unwrap().0.lock(), sender); + } + assert_eq!(tx.outputs().len(), 2); + let deposit_output = CellOutput::new_builder() + .capacity((120 * ONE_CKB).pack()) + .lock(sender.clone()) + .type_(Some(build_dao_script()).pack()) + .build(); + assert_eq!(tx.output(0).unwrap(), deposit_output); + assert_eq!(tx.output(1).unwrap().lock(), sender); + let expected_outputs_data = vec![Bytes::from(vec![0u8; 8]), Bytes::default()]; + let outputs_data = tx + .outputs_data() + .into_iter() + .map(|d| d.raw_data()) + .collect::>(); + assert_eq!(outputs_data, expected_outputs_data); + let witnesses_len = tx + .witnesses() + .into_iter() + .map(|w| w.raw_data().len()) + .collect::>(); + assert_eq!(witnesses_len, vec![placeholder_witness.as_slice().len(), 0]); + ctx.verify(tx, FEE_RATE).unwrap(); +} + +#[test] +fn test_dao_prepare() { + let sender = build_sighash_script(ACCOUNT1_ARG); + let (mut ctx, _) = init_context( + Vec::new(), + vec![ + (sender.clone(), Some(100 * ONE_CKB)), + (sender.clone(), Some(200 * ONE_CKB)), + (sender.clone(), Some(300 * ONE_CKB)), + ], + ); + + let deposit_point = (5, 5, 1000); + let deposit_number = deposit_point.0 * deposit_point.2 + deposit_point.1; + let deposit_point = + EpochNumberWithFraction::new(deposit_point.0, deposit_point.1, deposit_point.2); + + let deposit_input = CellInput::new(random_out_point(), 0); + let deposit_output = CellOutput::new_builder() + .capacity((220 * ONE_CKB).pack()) + .lock(sender.clone()) + .type_(Some(build_dao_script()).pack()) + .build(); + let deposit_header = HeaderBuilder::default() + .epoch(deposit_point.full_value().pack()) + .number(deposit_number.pack()) + .build(); + let deposit_block_hash = deposit_header.hash(); + ctx.add_live_cell( + deposit_input.clone(), + deposit_output.clone(), + Bytes::from(vec![0u8; 8]), + Some(deposit_block_hash.clone()), + ); + ctx.add_header(deposit_header); + + let builder = DaoPrepareBuilder::from(vec![deposit_input]); + let placeholder_witness = WitnessArgs::new_builder() + .lock(Some(Bytes::from(vec![0u8; 65])).pack()) + .build(); + let balancer = + CapacityBalancer::new_simple(sender.clone(), placeholder_witness.clone(), FEE_RATE); + + let account1_key = secp256k1::SecretKey::from_slice(ACCOUNT1_KEY.as_bytes()).unwrap(); + let signer = SecpCkbRawKeySigner::new_with_secret_keys(vec![account1_key]); + let script_unlocker = SecpSighashUnlocker::from(Box::new(signer) as Box<_>); + let mut unlockers: HashMap> = HashMap::default(); + unlockers.insert( + ScriptId::new_type(SIGHASH_TYPE_HASH.clone()), + Box::new(script_unlocker), + ); + + let mut cell_collector = ctx.to_live_cells_context(); + let (tx, locked_groups) = builder + .build_unlocked(&mut cell_collector, &ctx, &ctx, &ctx, &balancer, &unlockers) + .unwrap(); + + assert!(locked_groups.is_empty()); + assert_eq!( + tx.header_deps().into_iter().collect::>(), + vec![deposit_block_hash] + ); + assert_eq!(tx.cell_deps().len(), 2); + assert_eq!(tx.inputs().len(), 2); + for out_point in tx.input_pts_iter() { + assert_eq!(ctx.get_input(&out_point).unwrap().0.lock(), sender); + } + assert_eq!(tx.outputs().len(), 2); + assert_eq!(tx.output(0).unwrap(), deposit_output); + assert_eq!(tx.output(1).unwrap().lock(), sender); + let expected_outputs_data = vec![ + Bytes::from(deposit_number.to_le_bytes().to_vec()), + Bytes::default(), + ]; + let outputs_data = tx + .outputs_data() + .into_iter() + .map(|d| d.raw_data()) + .collect::>(); + assert_eq!(outputs_data, expected_outputs_data); + let witnesses_len = tx + .witnesses() + .into_iter() + .map(|w| w.raw_data().len()) + .collect::>(); + assert_eq!(witnesses_len, vec![placeholder_witness.as_slice().len(), 0]); + ctx.verify(tx, FEE_RATE).unwrap(); +} + +#[test] +fn test_dao_withdraw() { + let sender = build_sighash_script(ACCOUNT1_ARG); + let (mut ctx, _) = init_context( + Vec::new(), + vec![ + (sender.clone(), Some(100 * ONE_CKB)), + (sender.clone(), Some(200 * ONE_CKB)), + (sender.clone(), Some(300 * ONE_CKB)), + ], + ); + + let (deposit_point, prepare_point) = ((5, 5, 1000), (184, 4, 1000)); + let deposit_number = deposit_point.0 * deposit_point.2 + deposit_point.1; + let prepare_number = prepare_point.0 * prepare_point.2 + prepare_point.1; + let deposit_point = + EpochNumberWithFraction::new(deposit_point.0, deposit_point.1, deposit_point.2); + let prepare_point = + EpochNumberWithFraction::new(prepare_point.0, prepare_point.1, prepare_point.2); + let deposit_header = HeaderBuilder::default() + .epoch(deposit_point.full_value().pack()) + .number(deposit_number.pack()) + .dao(pack_dao_data( + 10_000_000_000_123_456, + Default::default(), + Default::default(), + Default::default(), + )) + .build(); + let prepare_header = HeaderBuilder::default() + .epoch(prepare_point.full_value().pack()) + .number(prepare_number.pack()) + .dao(pack_dao_data( + 10_000_000_001_123_456, + Default::default(), + Default::default(), + Default::default(), + )) + .build(); + let deposit_block_hash = deposit_header.hash(); + let prepare_block_hash = prepare_header.hash(); + + let unlock_point = minimal_unlock_point(&deposit_header, &prepare_header); + let since = Since::new( + SinceType::EpochNumberWithFraction, + unlock_point.full_value(), + false, + ); + let prepare_out_point = random_out_point(); + let prepare_input = CellInput::new(prepare_out_point.clone(), since.value()); + let prepare_output = CellOutput::new_builder() + .capacity((220 * ONE_CKB).pack()) + .lock(sender.clone()) + .type_(Some(build_dao_script()).pack()) + .build(); + ctx.add_live_cell( + prepare_input, + prepare_output.clone(), + Bytes::from(deposit_number.to_le_bytes().to_vec()), + Some(prepare_block_hash.clone()), + ); + ctx.add_header(deposit_header.clone()); + ctx.add_header(prepare_header.clone()); + + let placeholder_witness = WitnessArgs::new_builder() + .lock(Some(Bytes::from(vec![0u8; 65])).pack()) + .build(); + let withdraw_item = DaoWithdrawItem::new(prepare_out_point, Some(placeholder_witness.clone())); + let withdraw_receiver = DaoWithdrawReceiver::LockScript { + script: sender.clone(), + fee_rate: None, + }; + let builder = DaoWithdrawBuilder::new(vec![withdraw_item], withdraw_receiver); + let balancer = + CapacityBalancer::new_simple(sender.clone(), placeholder_witness.clone(), FEE_RATE); + + let account1_key = secp256k1::SecretKey::from_slice(ACCOUNT1_KEY.as_bytes()).unwrap(); + let signer = SecpCkbRawKeySigner::new_with_secret_keys(vec![account1_key]); + let script_unlocker = SecpSighashUnlocker::from(Box::new(signer) as Box<_>); + let mut unlockers: HashMap> = HashMap::default(); + unlockers.insert( + ScriptId::new_type(SIGHASH_TYPE_HASH.clone()), + Box::new(script_unlocker), + ); + + let mut cell_collector = ctx.to_live_cells_context(); + let (tx, locked_groups) = builder + .build_unlocked(&mut cell_collector, &ctx, &ctx, &ctx, &balancer, &unlockers) + .unwrap(); + + assert!(locked_groups.is_empty()); + assert_eq!( + tx.header_deps().into_iter().collect::>(), + vec![deposit_block_hash, prepare_block_hash] + ); + assert_eq!(tx.cell_deps().len(), 2); + assert_eq!(tx.inputs().len(), 2); + for out_point in tx.input_pts_iter() { + assert_eq!(ctx.get_input(&out_point).unwrap().0.lock(), sender); + } + assert_eq!(tx.outputs().len(), 2); + let occupied_capacity = prepare_output + .occupied_capacity(Capacity::bytes(8).unwrap()) + .unwrap() + .as_u64(); + let expected_capacity = calculate_dao_maximum_withdraw4( + &deposit_header, + &prepare_header, + &prepare_output, + occupied_capacity, + ); + let expected_output = prepare_output + .as_builder() + .capacity(expected_capacity.pack()) + .type_(ScriptOpt::default()) + .build(); + assert_eq!(tx.output(0).unwrap(), expected_output); + assert_eq!(tx.output(1).unwrap().lock(), sender); + let expected_outputs_data = vec![Bytes::default(), Bytes::default()]; + let outputs_data = tx + .outputs_data() + .into_iter() + .map(|d| d.raw_data()) + .collect::>(); + assert_eq!(outputs_data, expected_outputs_data); + let witnesses_len = tx + .witnesses() + .into_iter() + .map(|w| w.raw_data().len()) + .collect::>(); + let witness = placeholder_witness + .as_builder() + .input_type(Some(Bytes::from(vec![0u8; 8])).pack()) + .build(); + assert_eq!(witnesses_len, vec![witness.as_slice().len(), 0]); + ctx.verify(tx, FEE_RATE).unwrap(); +} diff --git a/src/tests/tx_builder/mod.rs b/src/tests/tx_builder/mod.rs new file mode 100644 index 00000000..4e09562f --- /dev/null +++ b/src/tests/tx_builder/mod.rs @@ -0,0 +1,8 @@ +mod acp; +mod cheque; +mod cycle; +mod dao; +mod omni_lock; +pub mod omni_lock_util; +mod transfer; +mod udt; diff --git a/src/tests/omni_lock.rs b/src/tests/tx_builder/omni_lock.rs similarity index 97% rename from src/tests/omni_lock.rs rename to src/tests/tx_builder/omni_lock.rs index 0338d44e..ef73df0e 100644 --- a/src/tests/omni_lock.rs +++ b/src/tests/tx_builder/omni_lock.rs @@ -4,9 +4,10 @@ use crate::{ constants::{ONE_CKB, SIGHASH_TYPE_HASH}, test_util::random_out_point, tests::{ - build_sighash_script, init_context, omni_lock_util::generate_rc, ACCOUNT0_ARG, - ACCOUNT0_KEY, ACCOUNT1_ARG, ACCOUNT1_KEY, ACCOUNT2_ARG, ACCOUNT2_KEY, ACCOUNT3_ARG, - ACCOUNT3_KEY, ALWAYS_SUCCESS_BIN, FEE_RATE, SUDT_BIN, + build_omnilock_script, build_sighash_script, init_context, + tx_builder::omni_lock_util::generate_rc, ACCOUNT0_ARG, ACCOUNT0_KEY, ACCOUNT1_ARG, + ACCOUNT1_KEY, ACCOUNT2_ARG, ACCOUNT2_KEY, ACCOUNT3_ARG, ACCOUNT3_KEY, ALWAYS_SUCCESS_BIN, + FEE_RATE, OMNILOCK_BIN, SUDT_BIN, }, traits::{CellDepResolver, SecpCkbRawKeySigner}, tx_builder::{ @@ -14,7 +15,7 @@ use crate::{ balance_tx_capacity, fill_placeholder_witnesses, omni_lock::OmniLockTransferBuilder, udt::{UdtTargetReceiver, UdtTransferBuilder}, - CapacityProvider, TransferAction, + unlock_tx, CapacityBalancer, CapacityProvider, TransferAction, TxBuilder, }, types::xudt_rce_mol::SmtProofEntryVec, unlock::{ @@ -27,7 +28,6 @@ use crate::{ ScriptId, Since, }; -use crate::tx_builder::{unlock_tx, CapacityBalancer, TxBuilder}; use ckb_crypto::secp::{Pubkey, SECP256K1}; use ckb_hash::blake2b_256; use ckb_types::{ @@ -39,17 +39,6 @@ use ckb_types::{ }; use rand::Rng; -const OMNILOCK_BIN: &[u8] = include_bytes!("../test-data/omni_lock"); - -fn build_omnilock_script(cfg: &OmniLockConfig) -> Script { - let omnilock_data_hash = H256::from(blake2b_256(OMNILOCK_BIN)); - Script::new_builder() - .code_hash(omnilock_data_hash.pack()) - .hash_type(ScriptHashType::Data1.into()) - .args(cfg.build_args().pack()) - .build() -} - fn build_omnilock_unlockers( key: secp256k1::SecretKey, config: OmniLockConfig, @@ -96,7 +85,7 @@ fn test_omnilock_simple_hash(cfg: OmniLockConfig) { let sender = build_omnilock_script(&cfg); let receiver = build_sighash_script(ACCOUNT2_ARG); - let ctx = init_context( + let (ctx, _) = init_context( vec![(OMNILOCK_BIN, true)], vec![ (sender.clone(), Some(100 * ONE_CKB)), @@ -117,16 +106,19 @@ fn test_omnilock_simple_hash(cfg: OmniLockConfig) { let mut cell_collector = ctx.to_live_cells_context(); let account2_key = secp256k1::SecretKey::from_slice(ACCOUNT0_KEY.as_bytes()).unwrap(); - let unlockers = build_omnilock_unlockers(account2_key, cfg.clone(), unlock_mode); + let unlockers = build_omnilock_unlockers(account2_key, cfg, unlock_mode); let mut tx = builder .build_balanced(&mut cell_collector, &ctx, &ctx, &ctx, &balancer, &unlockers) .unwrap(); - let unlockers = build_omnilock_unlockers(account2_key, cfg, unlock_mode); let (new_tx, new_locked_groups) = unlock_tx(tx.clone(), &ctx, &unlockers).unwrap(); assert!(new_locked_groups.is_empty()); tx = new_tx; + let json_tx: ckb_jsonrpc_types::TransactionView = + ckb_jsonrpc_types::TransactionView::from(tx.clone()); + println!("tx: {}", serde_json::to_string_pretty(&json_tx).unwrap()); + assert_eq!(tx.header_deps().len(), 0); assert_eq!(tx.cell_deps().len(), 1); assert_eq!(tx.inputs().len(), 2); @@ -198,7 +190,7 @@ fn test_omnilock_simple_hash_rc_input(mut cfg: OmniLockConfig) { let unlock_mode = OmniUnlockMode::Admin; let receiver = build_sighash_script(ACCOUNT2_ARG); - let mut ctx = init_context( + let (mut ctx, _) = init_context( vec![(OMNILOCK_BIN, true), (ALWAYS_SUCCESS_BIN, false)], vec![], ); @@ -384,7 +376,7 @@ fn test_omnilock_transfer_from_ethereum_wl_admin() { fn test_omnilock_simple_hash_rc(mut cfg: OmniLockConfig, unlock_mode: OmniUnlockMode) { let receiver = build_sighash_script(ACCOUNT2_ARG); - let mut ctx = init_context(vec![(OMNILOCK_BIN, true)], vec![]); + let (mut ctx, _) = init_context(vec![(OMNILOCK_BIN, true)], vec![]); let (rce_cells, rce_cells_len) = match unlock_mode { OmniUnlockMode::Admin => { let mut admin_config = cfg.get_admin_config().unwrap().clone(); @@ -498,7 +490,7 @@ fn test_omnilock_simple_hash_rc2(mut cfg: OmniLockConfig) { let unlock_mode = OmniUnlockMode::Admin; let receiver = build_sighash_script(ACCOUNT2_ARG); - let mut ctx = init_context(vec![(OMNILOCK_BIN, true)], vec![]); + let (mut ctx, _) = init_context(vec![(OMNILOCK_BIN, true)], vec![]); let alternative_auth = build_alternative_auth(ACCOUNT1_KEY.as_bytes(), IdentityFlag::PubkeyHash); let (proof_vec, rc_type_id, rce_cells) = generate_rc( @@ -592,7 +584,7 @@ fn test_omnilock_transfer_from_multisig() { let sender = build_omnilock_script(&cfg); let receiver = build_sighash_script(ACCOUNT2_ARG); - let ctx = init_context( + let (ctx, _) = init_context( vec![(OMNILOCK_BIN, true)], vec![ (sender.clone(), Some(100 * ONE_CKB)), @@ -675,7 +667,7 @@ fn test_omnilock_transfer_from_multisig_wl_commnon(unlock_mode: OmniUnlockMode) ]; let multi_cfg = MultisigConfig::new_with(lock_args, 0, 2).unwrap(); let admin_id = Identity::new_multisig(multi_cfg.clone()); - let mut ctx = init_context(vec![(OMNILOCK_BIN, true)], vec![]); + let (mut ctx, _) = init_context(vec![(OMNILOCK_BIN, true)], vec![]); let (proof_vec, rc_type_id, rce_cells) = generate_rc(&mut ctx, admin_id.to_smt_key().into(), false, ACCOUNT0_ARG); cfg.set_admin_config(AdminConfig::new( @@ -773,7 +765,7 @@ fn test_omnilock_transfer_from_ownerlock() { let cfg = OmniLockConfig::new_ownerlock(hash); let sender0 = build_omnilock_script(&cfg); - let ctx = init_context( + let (ctx, _) = init_context( vec![(OMNILOCK_BIN, true)], vec![ (sender0.clone(), Some(50 * ONE_CKB)), @@ -818,6 +810,9 @@ fn test_omnilock_transfer_from_ownerlock() { .build_balanced(&mut cell_collector, &ctx, &ctx, &ctx, &balancer, &unlockers) .unwrap(); + let json_tx = ckb_jsonrpc_types::TransactionView::from(tx.clone()); + println!("tx: {}", serde_json::to_string_pretty(&json_tx).unwrap()); + let (new_tx, new_locked_groups) = unlock_tx(tx.clone(), &ctx, &unlockers).unwrap(); assert!(new_locked_groups.is_empty()); tx = new_tx; @@ -857,7 +852,7 @@ fn test_omnilock_transfer_from_ownerlock_wl_admin() { let mut cfg = OmniLockConfig::new_ownerlock(hash); let owner_sender = build_sighash_script(ACCOUNT3_ARG); - let mut ctx = init_context( + let (mut ctx, _) = init_context( vec![(OMNILOCK_BIN, true)], vec![(owner_sender.clone(), Some(61 * ONE_CKB))], ); @@ -980,7 +975,7 @@ fn test_omnilock_transfer_from_acp() { let unlock_mode = OmniUnlockMode::Normal; let sender = build_omnilock_script(&cfg); - let ctx = init_context( + let (ctx, _) = init_context( vec![(OMNILOCK_BIN, true)], vec![ (sender.clone(), Some(100 * ONE_CKB)), @@ -1052,7 +1047,7 @@ fn test_omnilock_transfer_to_acp() { let unlock_mode = OmniUnlockMode::Normal; let receiver = build_omnilock_script(&cfg); - let ctx = init_context( + let (ctx, _) = init_context( vec![(OMNILOCK_BIN, true)], vec![ (sender.clone(), Some(100 * ONE_CKB)), @@ -1143,7 +1138,7 @@ fn test_omnilock_udt_transfer() { .hash_type(ScriptHashType::Data1.into()) .args(owner.calc_script_hash().as_bytes().pack()) .build(); - let mut ctx = init_context( + let (mut ctx, _) = init_context( vec![(OMNILOCK_BIN, true), (SUDT_BIN, false)], vec![ // transaction fee pool @@ -1253,7 +1248,7 @@ fn test_omnilock_simple_hash_timelock(mut cfg: OmniLockConfig) { let sender = build_omnilock_script(&cfg); let receiver = build_sighash_script(ACCOUNT2_ARG); - let mut ctx = init_context(vec![(OMNILOCK_BIN, true)], vec![]); + let (mut ctx, _) = init_context(vec![(OMNILOCK_BIN, true)], vec![]); let prepare_out_point = random_out_point(); let prepare_input = CellInput::new(prepare_out_point, since.value()); @@ -1353,7 +1348,7 @@ fn test_omnilock_sudt_supply() { let sender = build_omnilock_script(&cfg); let sudt_script = build_sudt_script(sender.calc_script_hash()); - let mut ctx = init_context( + let (mut ctx, _) = init_context( vec![ (OMNILOCK_BIN, true), (SUDT_BIN, false), diff --git a/src/tests/omni_lock_util.rs b/src/tests/tx_builder/omni_lock_util.rs similarity index 98% rename from src/tests/omni_lock_util.rs rename to src/tests/tx_builder/omni_lock_util.rs index 1c2632fc..f06bb5be 100644 --- a/src/tests/omni_lock_util.rs +++ b/src/tests/tx_builder/omni_lock_util.rs @@ -1,17 +1,15 @@ -use bytes::Bytes; -use ckb_types::core::{DepType, ScriptHashType}; - use crate::constants::ONE_CKB; use crate::test_util::{random_out_point, Context}; +use crate::tests::{build_sighash_script, ALWAYS_SUCCESS_BIN}; use crate::types::xudt_rce_mol::{RCCellVecBuilder, RCDataBuilder, RCDataUnion, SmtProofEntryVec}; use crate::unlock::rc_data::ListType; use crate::unlock::rc_data::{Mask, RcRuleVecBuilder}; +use bytes::Bytes; +use ckb_types::core::{DepType, ScriptHashType}; use ckb_types::{packed::*, prelude::*, H160}; use sparse_merkle_tree::H256 as SmtH256; -use super::{build_sighash_script, ALWAYS_SUCCESS_BIN}; - pub fn generate_rc( ctx: &mut Context, smt_key: SmtH256, @@ -53,6 +51,7 @@ pub fn build_always_success_script() -> Script { // * build RCE cell, is_type = true. Only the Script.code_hash is kept for further use. // when in this case, to make "args" passed in unique // when in_input_cell is on, the cell is not in deps but in input. +#[allow(clippy::useless_vec)] fn build_script( ctx: &mut Context, is_type: bool, diff --git a/src/tests/tx_builder/transfer.rs b/src/tests/tx_builder/transfer.rs new file mode 100644 index 00000000..c71a998f --- /dev/null +++ b/src/tests/tx_builder/transfer.rs @@ -0,0 +1,207 @@ +use std::collections::HashMap; + +use ckb_hash::blake2b_256; +use ckb_types::{ + bytes::Bytes, + core::ScriptHashType, + packed::{CellOutput, Script, WitnessArgs}, + prelude::*, + H256, +}; + +use crate::tests::{ + build_multisig_script, build_multisig_unlockers, build_sighash_script, init_context, + ACCOUNT0_ARG, ACCOUNT0_KEY, ACCOUNT1_ARG, ACCOUNT1_KEY, ACCOUNT2_ARG, ACCOUNT2_KEY, ACP_BIN, + FEE_RATE, ONE_CKB, SIGHASH_TYPE_HASH, +}; +use crate::traits::SecpCkbRawKeySigner; +use crate::tx_builder::{ + transfer::CapacityTransferBuilder, unlock_tx, CapacityBalancer, TxBuilder, +}; +use crate::unlock::{AcpUnlocker, MultisigConfig, ScriptUnlocker, SecpSighashUnlocker}; +use crate::ScriptId; + +#[test] +fn test_transfer_from_sighash() { + let sender = build_sighash_script(ACCOUNT1_ARG); + let receiver = build_sighash_script(ACCOUNT2_ARG); + let (ctx, _) = init_context( + Vec::new(), + vec![ + (sender.clone(), Some(100 * ONE_CKB)), + (sender.clone(), Some(200 * ONE_CKB)), + (sender.clone(), Some(300 * ONE_CKB)), + ], + ); + + let output = CellOutput::new_builder() + .capacity((120 * ONE_CKB).pack()) + .lock(receiver) + .build(); + let builder = CapacityTransferBuilder::new(vec![(output.clone(), Bytes::default())]); + let placeholder_witness = WitnessArgs::new_builder() + .lock(Some(Bytes::from(vec![0u8; 65])).pack()) + .build(); + let balancer = + CapacityBalancer::new_simple(sender.clone(), placeholder_witness.clone(), FEE_RATE); + + let account1_key = secp256k1::SecretKey::from_slice(ACCOUNT1_KEY.as_bytes()).unwrap(); + let signer = SecpCkbRawKeySigner::new_with_secret_keys(vec![account1_key]); + let script_unlocker = SecpSighashUnlocker::from(Box::new(signer) as Box<_>); + let mut unlockers: HashMap> = HashMap::default(); + unlockers.insert( + ScriptId::new_type(SIGHASH_TYPE_HASH.clone()), + Box::new(script_unlocker), + ); + + let mut cell_collector = ctx.to_live_cells_context(); + let (tx, locked_groups) = builder + .build_unlocked(&mut cell_collector, &ctx, &ctx, &ctx, &balancer, &unlockers) + .unwrap(); + + assert!(locked_groups.is_empty()); + assert_eq!(tx.header_deps().len(), 0); + assert_eq!(tx.cell_deps().len(), 1); + assert_eq!(tx.inputs().len(), 2); + for out_point in tx.input_pts_iter() { + assert_eq!(ctx.get_input(&out_point).unwrap().0.lock(), sender); + } + assert_eq!(tx.outputs().len(), 2); + assert_eq!(tx.output(0).unwrap(), output); + assert_eq!(tx.output(1).unwrap().lock(), sender); + let witnesses_len = tx + .witnesses() + .into_iter() + .map(|w| w.raw_data().len()) + .collect::>(); + assert_eq!(witnesses_len, vec![placeholder_witness.as_slice().len(), 0]); + ctx.verify(tx, FEE_RATE).unwrap(); +} + +#[test] +fn test_transfer_from_multisig() { + let lock_args = vec![ + ACCOUNT0_ARG.clone(), + ACCOUNT1_ARG.clone(), + ACCOUNT2_ARG.clone(), + ]; + let cfg = MultisigConfig::new_with(lock_args, 0, 2).unwrap(); + + let sender = build_multisig_script(&cfg); + let receiver = build_sighash_script(ACCOUNT2_ARG); + + let (ctx, _) = init_context( + Vec::new(), + vec![ + (sender.clone(), Some(100 * ONE_CKB)), + (sender.clone(), Some(200 * ONE_CKB)), + (sender.clone(), Some(300 * ONE_CKB)), + ], + ); + + let output = CellOutput::new_builder() + .capacity((120 * ONE_CKB).pack()) + .lock(receiver) + .build(); + let builder = CapacityTransferBuilder::new(vec![(output.clone(), Bytes::default())]); + let placeholder_witness = cfg.placeholder_witness(); + let balancer = + CapacityBalancer::new_simple(sender.clone(), placeholder_witness.clone(), FEE_RATE); + + let mut cell_collector = ctx.to_live_cells_context(); + let account0_key = secp256k1::SecretKey::from_slice(ACCOUNT0_KEY.as_bytes()).unwrap(); + let account2_key = secp256k1::SecretKey::from_slice(ACCOUNT2_KEY.as_bytes()).unwrap(); + let unlockers = build_multisig_unlockers(account0_key, cfg.clone()); + let mut tx = builder + .build_balanced(&mut cell_collector, &ctx, &ctx, &ctx, &balancer, &unlockers) + .unwrap(); + + let mut locked_groups = None; + for key in [account0_key, account2_key] { + let unlockers = build_multisig_unlockers(key, cfg.clone()); + let (new_tx, new_locked_groups) = unlock_tx(tx.clone(), &ctx, &unlockers).unwrap(); + tx = new_tx; + locked_groups = Some(new_locked_groups); + } + + assert_eq!(locked_groups, Some(Vec::new())); + assert_eq!(tx.header_deps().len(), 0); + assert_eq!(tx.cell_deps().len(), 1); + assert_eq!(tx.inputs().len(), 2); + for out_point in tx.input_pts_iter() { + assert_eq!(ctx.get_input(&out_point).unwrap().0.lock(), sender); + } + assert_eq!(tx.outputs().len(), 2); + assert_eq!(tx.output(0).unwrap(), output); + assert_eq!(tx.output(1).unwrap().lock(), sender); + let witnesses = tx + .witnesses() + .into_iter() + .map(|w| w.raw_data()) + .collect::>(); + assert_eq!(witnesses.len(), 2); + assert_eq!(witnesses[0].len(), placeholder_witness.as_slice().len()); + assert_eq!(witnesses[1].len(), 0); + ctx.verify(tx, FEE_RATE).unwrap(); +} + +#[test] +fn test_transfer_from_acp() { + let data_hash = H256::from(blake2b_256(ACP_BIN)); + let sender = Script::new_builder() + .code_hash(data_hash.pack()) + .hash_type(ScriptHashType::Data1.into()) + .args(Bytes::from(ACCOUNT1_ARG.0.to_vec()).pack()) + .build(); + let receiver = build_sighash_script(ACCOUNT2_ARG); + let (ctx, _) = init_context( + vec![(ACP_BIN, true)], + vec![ + (sender.clone(), Some(100 * ONE_CKB)), + (sender.clone(), Some(200 * ONE_CKB)), + (sender.clone(), Some(300 * ONE_CKB)), + ], + ); + + let output = CellOutput::new_builder() + .capacity((120 * ONE_CKB).pack()) + .lock(receiver) + .build(); + let builder = CapacityTransferBuilder::new(vec![(output.clone(), Bytes::default())]); + let placeholder_witness = WitnessArgs::new_builder() + .lock(Some(Bytes::from(vec![0u8; 65])).pack()) + .build(); + let balancer = + CapacityBalancer::new_simple(sender.clone(), placeholder_witness.clone(), FEE_RATE); + + let account1_key = secp256k1::SecretKey::from_slice(ACCOUNT1_KEY.as_bytes()).unwrap(); + let signer = SecpCkbRawKeySigner::new_with_secret_keys(vec![account1_key]); + let script_unlocker = AcpUnlocker::from(Box::new(signer) as Box<_>); + let mut unlockers: HashMap> = HashMap::default(); + unlockers.insert(ScriptId::new_data1(data_hash), Box::new(script_unlocker)); + + let mut cell_collector = ctx.to_live_cells_context(); + let (tx, locked_groups) = builder + .build_unlocked(&mut cell_collector, &ctx, &ctx, &ctx, &balancer, &unlockers) + .unwrap(); + + assert!(locked_groups.is_empty()); + assert_eq!(tx.header_deps().len(), 0); + assert_eq!(tx.cell_deps().len(), 1); + assert_eq!(tx.inputs().len(), 2); + for out_point in tx.input_pts_iter() { + assert_eq!(ctx.get_input(&out_point).unwrap().0.lock(), sender); + } + assert_eq!(tx.outputs().len(), 2); + assert_eq!(tx.output(0).unwrap(), output); + assert_eq!(tx.output(1).unwrap().lock(), sender); + let witnesses = tx + .witnesses() + .into_iter() + .map(|w| w.raw_data()) + .collect::>(); + assert_eq!(witnesses.len(), 2); + assert_eq!(witnesses[0].len(), placeholder_witness.as_slice().len()); + assert_eq!(witnesses[1].len(), 0); + ctx.verify(tx, FEE_RATE).unwrap(); +} diff --git a/src/tests/tx_builder/udt.rs b/src/tests/tx_builder/udt.rs new file mode 100644 index 00000000..4544edbc --- /dev/null +++ b/src/tests/tx_builder/udt.rs @@ -0,0 +1,212 @@ +use std::collections::HashMap; + +use ckb_hash::blake2b_256; +use ckb_types::{ + bytes::Bytes, + core::{Capacity, ScriptHashType}, + packed::{CellInput, CellOutput, Script, WitnessArgs}, + prelude::*, + H160, H256, +}; + +use crate::constants::{ONE_CKB, SIGHASH_TYPE_HASH}; +use crate::tests::{ + build_sighash_script, init_context, ACCOUNT1_ARG, ACCOUNT1_KEY, ACCOUNT2_ARG, ACP_BIN, + FEE_RATE, SUDT_BIN, +}; +use crate::traits::SecpCkbRawKeySigner; +use crate::tx_builder::{ + udt::{UdtIssueBuilder, UdtTargetReceiver, UdtTransferBuilder, UdtType}, + CapacityBalancer, TransferAction, TxBuilder, +}; +use crate::unlock::{AcpUnlocker, ScriptUnlocker, SecpSighashUnlocker}; +use crate::ScriptId; + +use crate::test_util::random_out_point; + +#[test] +fn test_udt_issue() { + let sudt_data_hash = H256::from(blake2b_256(SUDT_BIN)); + let owner = build_sighash_script(ACCOUNT1_ARG); + let receiver = build_sighash_script(ACCOUNT2_ARG); + let (ctx, _) = init_context( + vec![(SUDT_BIN, false)], + vec![ + (owner.clone(), Some(100 * ONE_CKB)), + (owner.clone(), Some(200 * ONE_CKB)), + (owner.clone(), Some(300 * ONE_CKB)), + ], + ); + + let sudt_script_id = ScriptId::new_data1(sudt_data_hash.clone()); + let udt_receiver = UdtTargetReceiver::new(TransferAction::Create, receiver.clone(), 500); + let builder = UdtIssueBuilder { + udt_type: UdtType::Sudt, + script_id: sudt_script_id, + owner: owner.clone(), + receivers: vec![udt_receiver], + }; + let placeholder_witness = WitnessArgs::new_builder() + .lock(Some(Bytes::from(vec![0u8; 65])).pack()) + .build(); + let balancer = + CapacityBalancer::new_simple(owner.clone(), placeholder_witness.clone(), FEE_RATE); + + let account1_key = secp256k1::SecretKey::from_slice(ACCOUNT1_KEY.as_bytes()).unwrap(); + let signer = SecpCkbRawKeySigner::new_with_secret_keys(vec![account1_key]); + let script_unlocker = SecpSighashUnlocker::from(Box::new(signer) as Box<_>); + let mut unlockers: HashMap> = HashMap::default(); + unlockers.insert( + ScriptId::new_type(SIGHASH_TYPE_HASH.clone()), + Box::new(script_unlocker), + ); + + let mut cell_collector = ctx.to_live_cells_context(); + let (tx, locked_groups) = builder + .build_unlocked(&mut cell_collector, &ctx, &ctx, &ctx, &balancer, &unlockers) + .unwrap(); + + assert!(locked_groups.is_empty()); + assert_eq!(tx.header_deps().len(), 0); + assert_eq!(tx.cell_deps().len(), 2); + assert_eq!(tx.inputs().len(), 2); + for out_point in tx.input_pts_iter() { + assert_eq!(ctx.get_input(&out_point).unwrap().0.lock(), owner); + } + assert_eq!(tx.outputs().len(), 2); + let type_script = Script::new_builder() + .code_hash(sudt_data_hash.pack()) + .hash_type(ScriptHashType::Data1.into()) + .args(owner.calc_script_hash().as_bytes().pack()) + .build(); + let output = CellOutput::new_builder() + .lock(receiver) + .type_(Some(type_script).pack()) + .build(); + let occupied_capacity = output + .occupied_capacity(Capacity::bytes(16).unwrap()) + .unwrap() + .as_u64(); + let output = output + .as_builder() + .capacity(occupied_capacity.pack()) + .build(); + assert_eq!(tx.output(0).unwrap(), output); + assert_eq!(tx.output(1).unwrap().lock(), owner); + let expected_outputs_data = vec![ + Bytes::from(500u128.to_le_bytes().to_vec()), + Bytes::default(), + ]; + let outputs_data = tx + .outputs_data() + .into_iter() + .map(|d| d.raw_data()) + .collect::>(); + assert_eq!(outputs_data, expected_outputs_data); + let witnesses_len = tx + .witnesses() + .into_iter() + .map(|w| w.raw_data().len()) + .collect::>(); + assert_eq!(witnesses_len, vec![placeholder_witness.as_slice().len(), 0]); + ctx.verify(tx, FEE_RATE).unwrap(); +} + +#[test] +fn test_udt_transfer() { + let acp_data_hash = H256::from(blake2b_256(ACP_BIN)); + let sudt_data_hash = H256::from(blake2b_256(SUDT_BIN)); + let sender = build_sighash_script(ACCOUNT1_ARG); + let owner = build_sighash_script(H160::default()); + let type_script = Script::new_builder() + .code_hash(sudt_data_hash.pack()) + .hash_type(ScriptHashType::Data1.into()) + .args(owner.calc_script_hash().as_bytes().pack()) + .build(); + let (mut ctx, _) = init_context( + vec![(ACP_BIN, true), (SUDT_BIN, false)], + vec![ + (sender.clone(), Some(100 * ONE_CKB)), + (sender.clone(), Some(200 * ONE_CKB)), + ], + ); + + let sender_input = CellInput::new(random_out_point(), 0); + let sender_output = CellOutput::new_builder() + .capacity((200 * ONE_CKB).pack()) + .lock(sender.clone()) + .type_(Some(type_script.clone()).pack()) + .build(); + let sender_data = Bytes::from(500u128.to_le_bytes().to_vec()); + ctx.add_live_cell(sender_input, sender_output.clone(), sender_data, None); + + let receiver_acp_lock = Script::new_builder() + .code_hash(acp_data_hash.pack()) + .hash_type(ScriptHashType::Data1.into()) + .args(Bytes::from(ACCOUNT2_ARG.0.to_vec()).pack()) + .build(); + let receiver_input = CellInput::new(random_out_point(), 0); + let receiver_output = CellOutput::new_builder() + .capacity((200 * ONE_CKB).pack()) + .lock(receiver_acp_lock.clone()) + .type_(Some(type_script.clone()).pack()) + .build(); + let receiver_data = Bytes::from(100u128.to_le_bytes().to_vec()); + ctx.add_live_cell(receiver_input, receiver_output.clone(), receiver_data, None); + + let udt_receiver = UdtTargetReceiver::new(TransferAction::Update, receiver_acp_lock, 300); + let builder = UdtTransferBuilder { + type_script, + sender: sender.clone(), + receivers: vec![udt_receiver], + }; + let placeholder_witness = WitnessArgs::new_builder() + .lock(Some(Bytes::from(vec![0u8; 65])).pack()) + .build(); + let balancer = CapacityBalancer::new_simple(sender, placeholder_witness.clone(), FEE_RATE); + + let account1_key = secp256k1::SecretKey::from_slice(ACCOUNT1_KEY.as_bytes()).unwrap(); + let signer = SecpCkbRawKeySigner::new_with_secret_keys(vec![account1_key]); + let script_unlocker = SecpSighashUnlocker::from(Box::new(signer) as Box<_>); + let acp_unlocker = AcpUnlocker::from(Box::::default() as Box<_>); + let mut unlockers: HashMap> = HashMap::default(); + unlockers.insert( + ScriptId::new_type(SIGHASH_TYPE_HASH.clone()), + Box::new(script_unlocker), + ); + unlockers.insert(ScriptId::new_data1(acp_data_hash), Box::new(acp_unlocker)); + + let mut cell_collector = ctx.to_live_cells_context(); + let (tx, locked_groups) = builder + .build_unlocked(&mut cell_collector, &ctx, &ctx, &ctx, &balancer, &unlockers) + .unwrap(); + + assert!(locked_groups.is_empty()); + assert_eq!(tx.header_deps().len(), 0); + assert_eq!(tx.cell_deps().len(), 3); + assert_eq!(tx.inputs().len(), 3); + let outputs = tx.outputs().into_iter().collect::>(); + assert_eq!(outputs.len(), 3); + assert_eq!(outputs[0..2], vec![sender_output, receiver_output]); + let expected_outputs_data = vec![ + Bytes::from(200u128.to_le_bytes().to_vec()), + Bytes::from(400u128.to_le_bytes().to_vec()), + Bytes::default(), + ]; + let outputs_data = tx + .outputs_data() + .into_iter() + .map(|d| d.raw_data()) + .collect::>(); + assert_eq!(outputs_data, expected_outputs_data); + let witnesses_len = tx + .witnesses() + .into_iter() + .map(|w| w.raw_data().len()) + .collect::>(); + assert_eq!( + witnesses_len, + vec![placeholder_witness.as_slice().len(), 0, 0] + ); + ctx.verify(tx, FEE_RATE).unwrap(); +} diff --git a/src/traits/default_impls.rs b/src/traits/default_impls.rs index dea184fc..c4cf63c7 100644 --- a/src/traits/default_impls.rs +++ b/src/traits/default_impls.rs @@ -5,6 +5,7 @@ use std::time::Duration; use anyhow::anyhow; use ckb_crypto::secp::Pubkey; +use ed25519_dalek::ed25519::signature::SignerMut; use lru::LruCache; use parking_lot::Mutex; use thiserror::Error; @@ -23,13 +24,6 @@ use super::{ offchain_impls::CollectResult, OffchainCellCollector, OffchainCellDepResolver, OffchainTransactionDependencyProvider, }; -use crate::rpc::ckb_indexer::{Order, SearchKey, Tip}; -use crate::rpc::{CkbRpcClient, IndexerRpcClient}; -use crate::traits::{ - CellCollector, CellCollectorError, CellDepResolver, CellQueryOptions, HeaderDepResolver, - LiveCell, QueryOrder, Signer, SignerError, TransactionDependencyError, - TransactionDependencyProvider, -}; use crate::types::ScriptId; use crate::util::{get_max_mature_number, serialize_signature, zeroize_privkey}; use crate::SECP256K1; @@ -40,6 +34,23 @@ use crate::{ }, util::keccak160, }; +use crate::{ + rpc::ckb_indexer::{Order, SearchKey, Tip}, + unlock::omni_lock::BTCSignVtype, + util::btc_auth, +}; +use crate::{ + rpc::{CkbRpcClient, IndexerRpcClient}, + util::eos_auth, +}; +use crate::{ + traits::{ + CellCollector, CellCollectorError, CellDepResolver, CellQueryOptions, HeaderDepResolver, + LiveCell, QueryOrder, Signer, SignerError, TransactionDependencyError, + TransactionDependencyProvider, + }, + util::blake160, +}; use ckb_resource::{ CODE_HASH_DAO, CODE_HASH_SECP256K1_BLAKE160_MULTISIG_ALL, CODE_HASH_SECP256K1_BLAKE160_SIGHASH_ALL, @@ -569,7 +580,7 @@ impl TransactionDependencyProvider for DefaultTransactionDependencyProvider { } } -/// A signer use secp256k1 raw key, the id is `blake160(pubkey)`. +/// A signer use secp256k1 raw key. #[derive(Default, Clone)] pub struct SecpCkbRawKeySigner { keys: HashMap, @@ -579,6 +590,7 @@ impl SecpCkbRawKeySigner { pub fn new(keys: HashMap) -> SecpCkbRawKeySigner { SecpCkbRawKeySigner { keys } } + pub fn new_with_secret_keys(keys: Vec) -> SecpCkbRawKeySigner { let mut signer = SecpCkbRawKeySigner::default(); for key in keys { @@ -586,6 +598,7 @@ impl SecpCkbRawKeySigner { } signer } + pub fn add_secret_key(&mut self, key: secp256k1::SecretKey) { let pubkey = secp256k1::PublicKey::from_secret_key(&SECP256K1, &key); let hash160 = H160::from_slice(&blake2b_256(&pubkey.serialize()[..])[0..20]) @@ -607,6 +620,34 @@ impl SecpCkbRawKeySigner { let hash160 = keccak160(Pubkey::from(pubkey).as_ref()); self.keys.insert(hash160, key); } + + pub fn new_with_btc_secret_key(keys: Vec, vtype: BTCSignVtype) -> Self { + let mut signer = SecpCkbRawKeySigner::default(); + for key in keys { + signer.add_btc_secret_key(key, vtype); + } + signer + } + + pub fn add_btc_secret_key(&mut self, key: secp256k1::SecretKey, vtype: BTCSignVtype) { + let pubkey = secp256k1::PublicKey::from_secret_key(&SECP256K1, &key); + let hash160 = btc_auth(&pubkey.into(), vtype); + self.keys.insert(hash160.into(), key); + } + + pub fn new_with_eos_secret_key(keys: Vec, vtype: BTCSignVtype) -> Self { + let mut signer = SecpCkbRawKeySigner::default(); + for key in keys { + signer.add_eos_secret_key(key, vtype); + } + signer + } + + pub fn add_eos_secret_key(&mut self, key: secp256k1::SecretKey, vtype: BTCSignVtype) { + let pubkey = secp256k1::PublicKey::from_secret_key(&SECP256K1, &key); + let hash160 = eos_auth(&pubkey.into(), vtype); + self.keys.insert(hash160.into(), key); + } } impl Signer for SecpCkbRawKeySigner { @@ -650,6 +691,56 @@ impl Drop for SecpCkbRawKeySigner { } } } + +/// A signer use ed25519 raw key +#[derive(Clone)] +pub struct Ed25519Signer { + keys: HashMap, +} + +impl Ed25519Signer { + pub fn new(keys: Vec) -> Self { + let mut res = HashMap::with_capacity(keys.len()); + for key in keys { + res.insert(blake160(key.verifying_key().as_bytes()), key); + } + Self { keys: res } + } +} + +impl Signer for Ed25519Signer { + fn match_id(&self, id: &[u8]) -> bool { + id.len() == 20 && self.keys.contains_key(&H160::from_slice(id).unwrap()) + } + + fn sign( + &self, + id: &[u8], + message: &[u8], + _recoverable: bool, + _tx: &TransactionView, + ) -> Result { + if !self.match_id(id) { + return Err(SignerError::IdNotFound); + } + if message.len() != 83 { + return Err(SignerError::InvalidMessage(format!( + "expected length: 83, got: {}", + message.len() + ))); + } + + let key = self.keys.get(&H160::from_slice(id).unwrap()).unwrap(); + let sig = key.clone().sign(message); + + let verifying_key = key.verifying_key(); + // 64 + 32 + let mut sig_plus_pubkey = sig.to_vec(); + sig_plus_pubkey.extend(verifying_key.as_bytes()); + Ok(Bytes::from(sig_plus_pubkey)) + } +} + #[cfg(test)] mod anyhow_tests { use anyhow::anyhow; diff --git a/src/traits/mod.rs b/src/traits/mod.rs index 82d68282..52d1f34b 100644 --- a/src/traits/mod.rs +++ b/src/traits/mod.rs @@ -59,7 +59,7 @@ pub enum SignerError { /// * secp256k1 eth signer /// * RSA signer /// * Hardware wallet signer -pub trait Signer { +pub trait Signer: DynClone { /// typecial id are blake160(pubkey) and keccak256(pubkey)[12..20] fn match_id(&self, id: &[u8]) -> bool; diff --git a/src/transaction/builder/mod.rs b/src/transaction/builder/mod.rs index f6edd0f6..38a815ca 100644 --- a/src/transaction/builder/mod.rs +++ b/src/transaction/builder/mod.rs @@ -3,14 +3,14 @@ use std::collections::HashMap; use super::{handler::HandlerContexts, input::TransactionInput}; use crate::{ core::TransactionBuilder, - traits::CellCollectorError, + traits::{CellCollectorError, LiveCell}, transaction::TransactionBuilderConfiguration, tx_builder::{BalanceTxCapacityError, TxBuilderError}, ScriptGroup, TransactionWithScriptGroups, }; use ckb_types::{ core::{Capacity, TransactionView}, - packed::{self, Byte32, CellOutput, Script}, + packed::{self, Byte32, CellOutput, OutPoint, Script}, prelude::{Builder, Entity, Pack, Unpack}, }; pub mod fee_calculator; @@ -143,9 +143,11 @@ fn inner_build< input_iter: I, configuration: &TransactionBuilderConfiguration, contexts: &HandlerContexts, + rc_cells: Vec, ) -> Result { let mut lock_groups: HashMap = HashMap::default(); let mut type_groups: HashMap = HashMap::default(); + let mut inputs: HashMap = HashMap::default(); // setup outputs' type script group for (output_idx, output) in tx.get_outputs().clone().iter().enumerate() { @@ -158,15 +160,55 @@ fn inner_build< } } + let rc_len = rc_cells.len(); + + for (input_index, input) in rc_cells.into_iter().enumerate() { + let input = TransactionInput::new(input, 0); + tx.input(input.cell_input()); + tx.witness(packed::Bytes::default()); + + inputs.insert( + input.live_cell.out_point.clone(), + ( + input.live_cell.output.clone(), + input.live_cell.output_data.clone(), + ), + ); + + let previous_output = input.previous_output(); + let lock_script = previous_output.lock(); + lock_groups + .entry(lock_script.calc_script_hash()) + .or_insert_with(|| ScriptGroup::from_lock_script(&lock_script)) + .input_indices + .push(input_index); + + if let Some(type_script) = previous_output.type_().to_opt() { + type_groups + .entry(type_script.calc_script_hash()) + .or_insert_with(|| ScriptGroup::from_type_script(&type_script)) + .input_indices + .push(input_index); + } + } + // setup change output and data change_builder.init(&mut tx); - // collect inputs - for (input_index, input) in input_iter.enumerate() { + for (mut input_index, input) in input_iter.enumerate() { + input_index += rc_len; let input = input?; tx.input(input.cell_input()); tx.witness(packed::Bytes::default()); + inputs.insert( + input.live_cell.out_point.clone(), + ( + input.live_cell.output.clone(), + input.live_cell.output_data.clone(), + ), + ); + let previous_output = input.previous_output(); let lock_script = previous_output.lock(); lock_groups @@ -203,7 +245,11 @@ fn inner_build< let tx_view = change_builder.finalize(tx); - return Ok(TransactionWithScriptGroups::new(tx_view, script_groups)); + return Ok(TransactionWithScriptGroups::new( + tx_view, + script_groups, + inputs, + )); } } diff --git a/src/transaction/builder/simple.rs b/src/transaction/builder/simple.rs index 4e6756f2..3753067a 100644 --- a/src/transaction/builder/simple.rs +++ b/src/transaction/builder/simple.rs @@ -1,5 +1,6 @@ use crate::{ core::TransactionBuilder, + traits::LiveCell, transaction::{ handler::HandlerContexts, input::InputIterator, TransactionBuilderConfiguration, }, @@ -24,6 +25,7 @@ pub struct SimpleTransactionBuilder { input_iter: InputIterator, /// The inner transaction builder tx: TransactionBuilder, + rc_cells: Vec, } impl SimpleTransactionBuilder { @@ -37,9 +39,14 @@ impl SimpleTransactionBuilder { configuration, input_iter, tx: TransactionBuilder::default(), + rc_cells: Vec::new(), } } + pub fn set_rc_cells(&mut self, rc_cells: Vec) { + self.rc_cells = rc_cells + } + /// Update the change lock script. pub fn set_change_lock(&mut self, lock_script: Script) { self.change_lock = lock_script; @@ -71,6 +78,7 @@ impl CkbTransactionBuilder for SimpleTransactionBuilder { configuration, input_iter, tx, + rc_cells, } = self; let change_builder = DefaultChangeBuilder { @@ -79,6 +87,13 @@ impl CkbTransactionBuilder for SimpleTransactionBuilder { inputs: Vec::new(), }; - inner_build(tx, change_builder, input_iter, &configuration, contexts) + inner_build( + tx, + change_builder, + input_iter, + &configuration, + contexts, + rc_cells, + ) } } diff --git a/src/transaction/builder/sudt.rs b/src/transaction/builder/sudt.rs index 5b9097f5..f0e7d8e6 100644 --- a/src/transaction/builder/sudt.rs +++ b/src/transaction/builder/sudt.rs @@ -169,7 +169,14 @@ impl CkbTransactionBuilder for SudtTransactionBuilder { }; if owner_mode { - inner_build(tx, change_builder, input_iter, &configuration, contexts) + inner_build( + tx, + change_builder, + input_iter, + &configuration, + contexts, + Default::default(), + ) } else { let sudt_type_script = build_sudt_type_script( configuration.network_info(), @@ -198,7 +205,14 @@ impl CkbTransactionBuilder for SudtTransactionBuilder { .to_le_bytes() .pack(); tx.set_output_data(tx.outputs_data.len() - 1, change_output_data); - return inner_build(tx, change_builder, input_iter, &configuration, contexts); + return inner_build( + tx, + change_builder, + input_iter, + &configuration, + contexts, + Default::default(), + ); } } diff --git a/src/transaction/handler/mod.rs b/src/transaction/handler/mod.rs index fa4799c8..4e636342 100644 --- a/src/transaction/handler/mod.rs +++ b/src/transaction/handler/mod.rs @@ -10,6 +10,7 @@ use self::{ }; pub mod multisig; +pub mod omnilock; pub mod sighash; pub mod sudt; pub mod typeid; @@ -75,3 +76,20 @@ impl HandlerContexts { self.contexts.push(context); } } + +#[allow(unused_macros)] +macro_rules! cell_dep { + ($hash: literal, $idx: expr, $dep_type: expr) => {{ + let out_point = ckb_types::packed::OutPoint::new_builder() + .tx_hash(ckb_types::h256!($hash).pack()) + .index($idx.pack()) + .build(); + ckb_types::packed::CellDep::new_builder() + .out_point(out_point) + .dep_type($dep_type.into()) + .build() + }}; +} + +#[allow(unused_imports)] +pub(crate) use cell_dep; diff --git a/src/transaction/handler/omnilock.rs b/src/transaction/handler/omnilock.rs new file mode 100644 index 00000000..85162753 --- /dev/null +++ b/src/transaction/handler/omnilock.rs @@ -0,0 +1,155 @@ +use ckb_types::{ + h256, + packed::{CellDep, OutPoint, Script}, + prelude::{Builder, Entity, Pack}, +}; + +use super::{HandlerContext, ScriptHandler}; +use crate::{ + core::TransactionBuilder, + tx_builder::TxBuilderError, + types::cobuild::{ + basic::{Message, SighashAll, SighashAllOnly}, + top_level::WitnessLayout, + }, + unlock::{OmniLockConfig, OmniUnlockMode}, + NetworkInfo, NetworkType, ScriptGroup, ScriptId, +}; +use lazy_static::lazy_static; + +pub struct OmnilockScriptHandler { + cell_deps: Vec, + lock_script_id: ScriptId, +} + +pub struct OmnilockScriptContext { + pub cfg: OmniLockConfig, + pub rce_cells: Option>, + pub unlock_mode: OmniUnlockMode, + pub rpc_url: String, +} + +impl OmnilockScriptContext { + pub fn new(cfg: OmniLockConfig, rpc_url: String) -> Self { + Self { + cfg, + rce_cells: None, + unlock_mode: OmniUnlockMode::Normal, + rpc_url, + } + } + + pub fn unlock_mode(mut self, unlock_mode: OmniUnlockMode) -> Self { + self.unlock_mode = unlock_mode; + self + } +} + +impl HandlerContext for OmnilockScriptContext {} + +impl OmnilockScriptHandler { + pub fn is_match(&self, script: &Script) -> bool { + ScriptId::from(script) == self.lock_script_id + } + + pub fn new_with_network(network: &NetworkInfo) -> Result { + let mut ret = Self { + cell_deps: vec![], + lock_script_id: ScriptId::default(), + }; + ret.init(network)?; + Ok(ret) + } + + pub fn set_cell_deps(&mut self, cell_deps: Vec) { + self.cell_deps = cell_deps; + } + + pub fn insert_cell_dep(&mut self, cell_dep: CellDep) { + self.cell_deps.push(cell_dep) + } + + pub fn set_lock_script_id(&mut self, lock_script_id: ScriptId) { + self.lock_script_id = lock_script_id; + } +} + +impl ScriptHandler for OmnilockScriptHandler { + fn build_transaction( + &self, + tx_builder: &mut TransactionBuilder, + script_group: &mut ScriptGroup, + context: &dyn HandlerContext, + ) -> Result { + if !self.is_match(&script_group.script) { + return Ok(false); + } + if let Some(args) = context.as_any().downcast_ref::() { + tx_builder.dedup_cell_deps(self.cell_deps.clone()); + let index = script_group.input_indices.first().unwrap(); + if args.cfg.enable_cobuild { + let lock_field = args.cfg.placeholder_witness_lock(args.unlock_mode)?; + + let witness = match &args.cfg.cobuild_message { + None => { + let sighash_all_only = SighashAllOnly::new_builder() + .seal( + [bytes::Bytes::copy_from_slice(&[0x00u8]), lock_field] + .concat() + .pack(), + ) + .build(); + let sighash_all_only = + WitnessLayout::new_builder().set(sighash_all_only).build(); + sighash_all_only.as_bytes().pack() + } + Some(msg) => { + let sighash_all = SighashAll::new_builder() + .message(Message::new_unchecked(msg.clone())) + .seal( + [bytes::Bytes::copy_from_slice(&[0x00u8]), lock_field] + .concat() + .pack(), + ) + .build(); + let sighash_all = WitnessLayout::new_builder().set(sighash_all).build(); + sighash_all.as_bytes().pack() + } + }; + tx_builder.set_witness(*index, witness); + } else { + let placeholder_witness = args.cfg.placeholder_witness(args.unlock_mode)?; + if let Some(lock) = placeholder_witness.lock().to_opt() { + tx_builder.set_witness_lock(*index, Some(lock.raw_data())); + } + } + + Ok(true) + } else { + Ok(false) + } + } + + fn init(&mut self, network: &NetworkInfo) -> Result<(), TxBuilderError> { + if network.network_type == NetworkType::Mainnet { + self.lock_script_id = MAINNET_OMNILOCK_SCRIPT_ID.clone(); + } else if network.network_type == NetworkType::Testnet { + self.lock_script_id = get_testnet_omnilock_script_id().clone(); + } else { + return Err(TxBuilderError::UnsupportedNetworkType(network.network_type)); + }; + Ok(()) + } +} + +lazy_static! { + pub static ref MAINNET_OMNILOCK_SCRIPT_ID: ScriptId = ScriptId::new_type(h256!( + "0x9b819793a64463aed77c615d6cb226eea5487ccfc0783043a587254cda2b6f26" + )); +} + +pub fn get_testnet_omnilock_script_id() -> ScriptId { + ScriptId::new_type(h256!( + "0xf329effd1c475a2978453c8600e1eaf0bc2087ee093c3ee64cc96ec6847752cb" + )) +} diff --git a/src/transaction/mod.rs b/src/transaction/mod.rs index 6852c392..3e8b8e4e 100644 --- a/src/transaction/mod.rs +++ b/src/transaction/mod.rs @@ -48,6 +48,15 @@ impl TransactionBuilderConfiguration { }) } + pub fn new_with_empty_handlers(network: NetworkInfo) -> Self { + Self { + network, + script_handlers: Vec::new(), + fee_rate: 1000, + estimate_tx_size: 128000, + } + } + fn generate_system_handlers( network: &NetworkInfo, ) -> Result>, TxBuilderError> { @@ -64,6 +73,9 @@ impl TransactionBuilderConfiguration { ) as Box<_>, Box::new(handler::sudt::SudtHandler::new_with_network(network)?) as Box<_>, Box::new(handler::typeid::TypeIdHandler) as Box<_>, + Box::new(handler::omnilock::OmnilockScriptHandler::new_with_network( + network, + )?) as Box<_>, ]; Ok(ret) } diff --git a/src/transaction/signer/mod.rs b/src/transaction/signer/mod.rs index e4279caf..3af341f8 100644 --- a/src/transaction/signer/mod.rs +++ b/src/transaction/signer/mod.rs @@ -1,16 +1,19 @@ -use ckb_types::{core, H256}; +use ckb_types::{core, packed, H256}; use std::collections::HashMap; use crate::{ constants, - unlock::{MultisigConfig, UnlockError}, - NetworkInfo, ScriptGroup, ScriptId, TransactionWithScriptGroups, + traits::TransactionDependencyProvider, + unlock::{MultisigConfig, OmniLockConfig, OmniUnlockMode, UnlockError}, + NetworkInfo, NetworkType, ScriptGroup, ScriptId, TransactionWithScriptGroups, }; use self::sighash::Secp256k1Blake160SighashAllSigner; use super::handler::Type2Any; + pub mod multisig; +pub mod omnilock; pub mod sighash; pub trait CKBScriptSigner { @@ -20,6 +23,7 @@ pub trait CKBScriptSigner { tx_view: &core::TransactionView, script_group: &ScriptGroup, context: &dyn SignContext, + inputs: &dyn TransactionDependencyProvider, ) -> Result; } @@ -68,6 +72,44 @@ impl SignContexts { Ok(Self::new_multisig(key, multisig_config)) } + pub fn new_omnilock( + keys: Vec, + omnilock_config: OmniLockConfig, + unlock_mode: OmniUnlockMode, + ) -> Self { + let omnilock_context = + omnilock::OmnilockSignerContext::new(keys, omnilock_config).unlock_mode(unlock_mode); + Self { + contexts: vec![Box::new(omnilock_context)], + } + } + + pub fn new_omnilock_solana( + key: Vec, + omnilock_config: OmniLockConfig, + unlock_mode: OmniUnlockMode, + ) -> Self { + let omnilock_context = + omnilock::OmnilockSignerContext::new_with_ed25519_key(key, omnilock_config) + .unlock_mode(unlock_mode); + Self { + contexts: vec![Box::new(omnilock_context)], + } + } + + pub fn new_omnilock_exec_dl_custom( + signer: T, + omnilock_config: OmniLockConfig, + unlock_mode: OmniUnlockMode, + ) -> Self { + let omnilock_context = + omnilock::OmnilockSignerContext::new_with_dl_exec_signer(signer, omnilock_config) + .unlock_mode(unlock_mode); + Self { + contexts: vec![Box::new(omnilock_context)], + } + } + #[inline] pub fn add_context(&mut self, context: Box) { self.contexts.push(context); @@ -79,7 +121,7 @@ pub struct TransactionSigner { } impl TransactionSigner { - pub fn new(_network: &NetworkInfo) -> Self { + pub fn new(network: &NetworkInfo) -> Self { let mut unlockers = HashMap::default(); let sighash_script_id = ScriptId::new_type(constants::SIGHASH_TYPE_HASH.clone()); @@ -93,9 +135,31 @@ impl TransactionSigner { Box::new(multisig::Secp256k1Blake160MultisigAllSigner {}) as Box<_>, ); + match network.network_type { + NetworkType::Mainnet => unlockers.insert( + crate::transaction::handler::omnilock::MAINNET_OMNILOCK_SCRIPT_ID.clone(), + Box::new(omnilock::OmnilockSigner {}) as Box<_>, + ), + NetworkType::Testnet => unlockers.insert( + crate::transaction::handler::omnilock::get_testnet_omnilock_script_id().clone(), + Box::new(omnilock::OmnilockSigner {}) as Box<_>, + ), + _ => unreachable!(), + }; + Self { unlockers } } + pub fn insert_unlocker( + mut self, + script_id: ScriptId, + unlocker: impl CKBScriptSigner + 'static, + ) -> Self { + self.unlockers + .insert(script_id, Box::new(unlocker) as Box<_>); + self + } + pub fn sign_transaction( &self, transaction: &mut TransactionWithScriptGroups, @@ -113,7 +177,14 @@ impl TransactionSigner { if !unlocker.match_context(context.as_ref()) { continue; } - tx = unlocker.sign_transaction(&tx, script_group, context.as_ref())?; + tx = unlocker.sign_transaction( + &tx, + script_group, + context.as_ref(), + &InputsProvider { + inputs: &transaction.inputs, + }, + )?; signed_groups_indices.push(idx); break; } @@ -123,3 +194,56 @@ impl TransactionSigner { Ok(signed_groups_indices) } } + +struct InputsProvider<'a> { + inputs: &'a HashMap, +} + +impl<'a> crate::traits::TransactionDependencyProvider for InputsProvider<'a> { + /// For verify certain cell belong to certain transaction + fn get_transaction( + &self, + _tx_hash: &packed::Byte32, + ) -> Result { + Err(crate::traits::TransactionDependencyError::NotFound( + "not support".to_string(), + )) + } + /// For get the output information of inputs or cell_deps, those cell should be live cell + fn get_cell( + &self, + out_point: &packed::OutPoint, + ) -> Result { + self.inputs.get(out_point).map(|a| a.0.clone()).ok_or( + crate::traits::TransactionDependencyError::NotFound("not found".to_string()), + ) + } + /// For get the output data information of inputs or cell_deps + fn get_cell_data( + &self, + out_point: &packed::OutPoint, + ) -> Result { + self.inputs.get(out_point).map(|a| a.1.clone()).ok_or( + crate::traits::TransactionDependencyError::NotFound("not found".to_string()), + ) + } + /// For get the header information of header_deps + fn get_header( + &self, + _block_hash: &packed::Byte32, + ) -> Result { + Err(crate::traits::TransactionDependencyError::NotFound( + "not support".to_string(), + )) + } + + /// For get_block_extension + fn get_block_extension( + &self, + _block_hash: &packed::Byte32, + ) -> Result, crate::traits::TransactionDependencyError> { + Err(crate::traits::TransactionDependencyError::NotFound( + "not support".to_string(), + )) + } +} diff --git a/src/transaction/signer/multisig.rs b/src/transaction/signer/multisig.rs index 282ae70f..3b67e4e3 100644 --- a/src/transaction/signer/multisig.rs +++ b/src/transaction/signer/multisig.rs @@ -1,7 +1,10 @@ use ckb_types::core; use crate::{ - traits::{dummy_impls::DummyTransactionDependencyProvider, SecpCkbRawKeySigner}, + traits::{ + dummy_impls::DummyTransactionDependencyProvider, SecpCkbRawKeySigner, + TransactionDependencyProvider, + }, unlock::{ MultisigConfig, ScriptUnlocker, SecpMultisigScriptSigner, SecpMultisigUnlocker, UnlockError, }, @@ -45,6 +48,7 @@ impl CKBScriptSigner for Secp256k1Blake160MultisigAllSigner { transaction: &core::TransactionView, script_group: &crate::ScriptGroup, context: &dyn super::SignContext, + _tx_dep_provider: &dyn TransactionDependencyProvider, ) -> Result { if let Some(args) = context .as_any() diff --git a/src/transaction/signer/omnilock.rs b/src/transaction/signer/omnilock.rs new file mode 100644 index 00000000..93286f08 --- /dev/null +++ b/src/transaction/signer/omnilock.rs @@ -0,0 +1,117 @@ +use ckb_types::core; + +use crate::{ + traits::{ + default_impls::Ed25519Signer, SecpCkbRawKeySigner, Signer, TransactionDependencyProvider, + }, + unlock::{ + IdentityFlag, OmniLockConfig, OmniLockScriptSigner, OmniLockUnlocker, OmniUnlockMode, + ScriptUnlocker, UnlockError, + }, +}; + +use super::{CKBScriptSigner, SignContext}; + +pub struct OmnilockSigner {} + +pub struct OmnilockSignerContext { + keys: Vec, + ed25519_key: Vec, + custom_signer: Option>, + cfg: OmniLockConfig, + unlock_mode: OmniUnlockMode, +} + +impl OmnilockSignerContext { + pub fn new(keys: Vec, cfg: OmniLockConfig) -> Self { + Self { + keys, + ed25519_key: Default::default(), + custom_signer: None, + cfg, + unlock_mode: OmniUnlockMode::Normal, + } + } + + pub fn new_with_ed25519_key(key: Vec, cfg: OmniLockConfig) -> Self { + Self { + keys: Default::default(), + ed25519_key: key, + custom_signer: None, + cfg, + unlock_mode: OmniUnlockMode::Normal, + } + } + + pub fn new_with_dl_exec_signer(signer: T, cfg: OmniLockConfig) -> Self { + Self { + keys: Default::default(), + ed25519_key: Default::default(), + custom_signer: Some(Box::new(signer)), + cfg, + unlock_mode: OmniUnlockMode::Normal, + } + } + + /// Default is Normal + pub fn unlock_mode(mut self, unlock_mode: OmniUnlockMode) -> Self { + self.unlock_mode = unlock_mode; + self + } + + pub fn build_omnilock_unlocker(&self) -> OmniLockUnlocker { + let signer: Box = match self.cfg.id().flag() { + IdentityFlag::Ethereum | IdentityFlag::EthereumDisplaying | IdentityFlag::Tron => { + Box::new(SecpCkbRawKeySigner::new_with_ethereum_secret_keys( + self.keys.clone(), + )) + } + IdentityFlag::Bitcoin | IdentityFlag::Dogecoin => { + Box::new(SecpCkbRawKeySigner::new_with_btc_secret_key( + self.keys.clone(), + self.cfg.btc_sign_vtype, + )) + } + IdentityFlag::Eos => Box::new(SecpCkbRawKeySigner::new_with_eos_secret_key( + self.keys.clone(), + self.cfg.btc_sign_vtype, + )), + IdentityFlag::Solana => Box::new(Ed25519Signer::new(self.ed25519_key.clone())), + IdentityFlag::Dl | IdentityFlag::Exec => { + let signer = self + .custom_signer + .as_ref() + .expect("must have custom signer"); + dyn_clone::clone_box(&**signer) + } + IdentityFlag::Multisig | IdentityFlag::PubkeyHash | IdentityFlag::OwnerLock => { + Box::new(SecpCkbRawKeySigner::new_with_secret_keys(self.keys.clone())) + } + }; + let omnilock_signer = OmniLockScriptSigner::new(signer, self.cfg.clone(), self.unlock_mode); + OmniLockUnlocker::new(omnilock_signer, self.cfg.clone()) + } +} + +impl SignContext for OmnilockSignerContext {} + +impl CKBScriptSigner for OmnilockSigner { + fn match_context(&self, context: &dyn SignContext) -> bool { + context.as_any().is::() + } + fn sign_transaction( + &self, + transaction: &core::TransactionView, + script_group: &crate::ScriptGroup, + context: &dyn super::SignContext, + tx_dep_provider: &dyn TransactionDependencyProvider, + ) -> Result { + if let Some(args) = context.as_any().downcast_ref::() { + let unlocker = args.build_omnilock_unlocker(); + let tx = unlocker.unlock(transaction, script_group, tx_dep_provider)?; + Ok(tx) + } else { + Err(UnlockError::SignContextTypeIncorrect) + } + } +} diff --git a/src/transaction/signer/sighash.rs b/src/transaction/signer/sighash.rs index 38d4e095..b564e434 100644 --- a/src/transaction/signer/sighash.rs +++ b/src/transaction/signer/sighash.rs @@ -1,7 +1,10 @@ use ckb_types::core; use crate::{ - traits::{dummy_impls::DummyTransactionDependencyProvider, SecpCkbRawKeySigner}, + traits::{ + dummy_impls::DummyTransactionDependencyProvider, SecpCkbRawKeySigner, + TransactionDependencyProvider, + }, unlock::{ScriptUnlocker, SecpSighashUnlocker, UnlockError}, }; @@ -32,6 +35,7 @@ impl CKBScriptSigner for Secp256k1Blake160SighashAllSigner { transaction: &core::TransactionView, script_group: &crate::ScriptGroup, context: &dyn super::SignContext, + _tx_dep_provider: &dyn TransactionDependencyProvider, ) -> Result { if let Some(args) = context .as_any() diff --git a/src/tx_builder/mod.rs b/src/tx_builder/mod.rs index d6482f85..71d8648a 100644 --- a/src/tx_builder/mod.rs +++ b/src/tx_builder/mod.rs @@ -12,8 +12,6 @@ use anyhow::anyhow; use ckb_chain_spec::consensus::Consensus; use ckb_script::{TransactionScriptsVerifier, TxVerifyEnv}; use ckb_traits::{CellDataProvider, ExtensionProvider, HeaderProvider}; -use thiserror::Error; - use ckb_types::core::cell::{CellProvider, HeaderChecker}; use ckb_types::core::HeaderView; use ckb_types::{ @@ -24,8 +22,8 @@ use ckb_types::{ packed::{Byte32, CellInput, CellOutput, Script, WitnessArgs}, prelude::*, }; +use thiserror::Error; -use crate::types::ScriptGroup; use crate::types::{HumanCapacity, ScriptId}; use crate::unlock::{ScriptUnlocker, UnlockError}; use crate::util::calculate_dao_maximum_withdraw4; @@ -37,6 +35,7 @@ use crate::{ }, RpcError, }; +use crate::{types::ScriptGroup, unlock::omni_lock::ConfigError}; /// Transaction builder errors #[derive(Error, Debug)] @@ -76,6 +75,9 @@ pub enum TxBuilderError { #[error("can not find specifed output to put small change")] NoOutputForSmallChange, + #[error("configuration error: `{0}`")] + ConfigError(#[from] ConfigError), + #[error("other error: `{0}`")] Other(anyhow::Error), } diff --git a/src/types/address.rs b/src/types/address.rs index 4fa6ad26..fbfb7db8 100644 --- a/src/types/address.rs +++ b/src/types/address.rs @@ -555,6 +555,8 @@ mod old_addr { #[cfg(test)] mod test { + use crate::util::hex_decode; + use super::*; use ckb_types::{h160, h256}; @@ -651,7 +653,7 @@ mod test { assert_eq!(addr.payload().hash_type(), ScriptHashType::Type); assert_eq!( addr.payload().args().as_ref(), - hex::decode("0c4bec5862af847a2d852cb939c6dfb70c25e52e").unwrap() + hex_decode("0c4bec5862af847a2d852cb939c6dfb70c25e52e".as_bytes()) ); } @@ -678,9 +680,9 @@ mod test { let mut data = vec![0u8; 23]; data[0] = 0x01; data[1] = CodeHashIndex::Sighash as u8; - data[2..].copy_from_slice( - &hex::decode("4fb2be2e5d0c1a3b8694f832350a33c1685d477a33").unwrap(), - ); + data[2..].copy_from_slice(&hex_decode( + "4fb2be2e5d0c1a3b8694f832350a33c1685d477a33".as_bytes(), + )); let variant = bech32::Variant::Bech32; let addr = bech32::encode("ckb", data.to_base32(), variant).unwrap(); let expected_addr = "ckb1qyqylv479ewscx3ms620sv34pgeuz6zagaarxdzvx03"; @@ -712,7 +714,7 @@ mod test { fn test_invalid_old_full_address() { // INVALID bech32 encoding { - let args = hex::decode("4fb2be2e5d0c1a3b86").unwrap(); + let args = hex_decode("4fb2be2e5d0c1a3b86".as_bytes()); let mut data = vec![0u8; 33 + args.len()]; data[0] = AddressType::FullData as u8; data[1..33].copy_from_slice( @@ -751,7 +753,7 @@ mod test { ] { let code_hash = h256!("0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8"); - let args = hex::decode("4fb2be2e5d0c1a3b86").unwrap(); + let args = hex_decode("4fb2be2e5d0c1a3b86".as_bytes()); let mut data = vec![0u8; 34 + args.len()]; data[0] = 0x00; data[1..33].copy_from_slice(code_hash.as_bytes()); diff --git a/src/types/cobuild/basic.rs b/src/types/cobuild/basic.rs new file mode 100644 index 00000000..695b4e6d --- /dev/null +++ b/src/types/cobuild/basic.rs @@ -0,0 +1,5203 @@ +// Generated by Molecule 0.7.5 + +use ckb_types::molecule; +// these lines above are manually added + +use super::blockchain::*; +use molecule::prelude::*; +#[derive(Clone)] +pub struct Hash(molecule::bytes::Bytes); +impl ::core::fmt::LowerHex for Hash { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + if f.alternate() { + write!(f, "0x")?; + } + write!(f, "{}", hex_string(self.as_slice())) + } +} +impl ::core::fmt::Debug for Hash { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}({:#x})", Self::NAME, self) + } +} +impl ::core::fmt::Display for Hash { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + let raw_data = hex_string(&self.raw_data()); + write!(f, "{}(0x{})", Self::NAME, raw_data) + } +} +impl ::core::default::Default for Hash { + fn default() -> Self { + let v = molecule::bytes::Bytes::from_static(&Self::DEFAULT_VALUE); + Hash::new_unchecked(v) + } +} +impl Hash { + const DEFAULT_VALUE: [u8; 32] = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, + ]; + pub const TOTAL_SIZE: usize = 32; + pub const ITEM_SIZE: usize = 1; + pub const ITEM_COUNT: usize = 32; + pub fn nth0(&self) -> Byte { + Byte::new_unchecked(self.0.slice(0..1)) + } + pub fn nth1(&self) -> Byte { + Byte::new_unchecked(self.0.slice(1..2)) + } + pub fn nth2(&self) -> Byte { + Byte::new_unchecked(self.0.slice(2..3)) + } + pub fn nth3(&self) -> Byte { + Byte::new_unchecked(self.0.slice(3..4)) + } + pub fn nth4(&self) -> Byte { + Byte::new_unchecked(self.0.slice(4..5)) + } + pub fn nth5(&self) -> Byte { + Byte::new_unchecked(self.0.slice(5..6)) + } + pub fn nth6(&self) -> Byte { + Byte::new_unchecked(self.0.slice(6..7)) + } + pub fn nth7(&self) -> Byte { + Byte::new_unchecked(self.0.slice(7..8)) + } + pub fn nth8(&self) -> Byte { + Byte::new_unchecked(self.0.slice(8..9)) + } + pub fn nth9(&self) -> Byte { + Byte::new_unchecked(self.0.slice(9..10)) + } + pub fn nth10(&self) -> Byte { + Byte::new_unchecked(self.0.slice(10..11)) + } + pub fn nth11(&self) -> Byte { + Byte::new_unchecked(self.0.slice(11..12)) + } + pub fn nth12(&self) -> Byte { + Byte::new_unchecked(self.0.slice(12..13)) + } + pub fn nth13(&self) -> Byte { + Byte::new_unchecked(self.0.slice(13..14)) + } + pub fn nth14(&self) -> Byte { + Byte::new_unchecked(self.0.slice(14..15)) + } + pub fn nth15(&self) -> Byte { + Byte::new_unchecked(self.0.slice(15..16)) + } + pub fn nth16(&self) -> Byte { + Byte::new_unchecked(self.0.slice(16..17)) + } + pub fn nth17(&self) -> Byte { + Byte::new_unchecked(self.0.slice(17..18)) + } + pub fn nth18(&self) -> Byte { + Byte::new_unchecked(self.0.slice(18..19)) + } + pub fn nth19(&self) -> Byte { + Byte::new_unchecked(self.0.slice(19..20)) + } + pub fn nth20(&self) -> Byte { + Byte::new_unchecked(self.0.slice(20..21)) + } + pub fn nth21(&self) -> Byte { + Byte::new_unchecked(self.0.slice(21..22)) + } + pub fn nth22(&self) -> Byte { + Byte::new_unchecked(self.0.slice(22..23)) + } + pub fn nth23(&self) -> Byte { + Byte::new_unchecked(self.0.slice(23..24)) + } + pub fn nth24(&self) -> Byte { + Byte::new_unchecked(self.0.slice(24..25)) + } + pub fn nth25(&self) -> Byte { + Byte::new_unchecked(self.0.slice(25..26)) + } + pub fn nth26(&self) -> Byte { + Byte::new_unchecked(self.0.slice(26..27)) + } + pub fn nth27(&self) -> Byte { + Byte::new_unchecked(self.0.slice(27..28)) + } + pub fn nth28(&self) -> Byte { + Byte::new_unchecked(self.0.slice(28..29)) + } + pub fn nth29(&self) -> Byte { + Byte::new_unchecked(self.0.slice(29..30)) + } + pub fn nth30(&self) -> Byte { + Byte::new_unchecked(self.0.slice(30..31)) + } + pub fn nth31(&self) -> Byte { + Byte::new_unchecked(self.0.slice(31..32)) + } + pub fn raw_data(&self) -> molecule::bytes::Bytes { + self.as_bytes() + } + pub fn as_reader<'r>(&'r self) -> HashReader<'r> { + HashReader::new_unchecked(self.as_slice()) + } +} +impl molecule::prelude::Entity for Hash { + type Builder = HashBuilder; + const NAME: &'static str = "Hash"; + fn new_unchecked(data: molecule::bytes::Bytes) -> Self { + Hash(data) + } + fn as_bytes(&self) -> molecule::bytes::Bytes { + self.0.clone() + } + fn as_slice(&self) -> &[u8] { + &self.0[..] + } + fn from_slice(slice: &[u8]) -> molecule::error::VerificationResult { + HashReader::from_slice(slice).map(|reader| reader.to_entity()) + } + fn from_compatible_slice(slice: &[u8]) -> molecule::error::VerificationResult { + HashReader::from_compatible_slice(slice).map(|reader| reader.to_entity()) + } + fn new_builder() -> Self::Builder { + ::core::default::Default::default() + } + fn as_builder(self) -> Self::Builder { + Self::new_builder().set([ + self.nth0(), + self.nth1(), + self.nth2(), + self.nth3(), + self.nth4(), + self.nth5(), + self.nth6(), + self.nth7(), + self.nth8(), + self.nth9(), + self.nth10(), + self.nth11(), + self.nth12(), + self.nth13(), + self.nth14(), + self.nth15(), + self.nth16(), + self.nth17(), + self.nth18(), + self.nth19(), + self.nth20(), + self.nth21(), + self.nth22(), + self.nth23(), + self.nth24(), + self.nth25(), + self.nth26(), + self.nth27(), + self.nth28(), + self.nth29(), + self.nth30(), + self.nth31(), + ]) + } +} +#[derive(Clone, Copy)] +pub struct HashReader<'r>(&'r [u8]); +impl<'r> ::core::fmt::LowerHex for HashReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + if f.alternate() { + write!(f, "0x")?; + } + write!(f, "{}", hex_string(self.as_slice())) + } +} +impl<'r> ::core::fmt::Debug for HashReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}({:#x})", Self::NAME, self) + } +} +impl<'r> ::core::fmt::Display for HashReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + let raw_data = hex_string(&self.raw_data()); + write!(f, "{}(0x{})", Self::NAME, raw_data) + } +} +impl<'r> HashReader<'r> { + pub const TOTAL_SIZE: usize = 32; + pub const ITEM_SIZE: usize = 1; + pub const ITEM_COUNT: usize = 32; + pub fn nth0(&self) -> ByteReader<'r> { + ByteReader::new_unchecked(&self.as_slice()[0..1]) + } + pub fn nth1(&self) -> ByteReader<'r> { + ByteReader::new_unchecked(&self.as_slice()[1..2]) + } + pub fn nth2(&self) -> ByteReader<'r> { + ByteReader::new_unchecked(&self.as_slice()[2..3]) + } + pub fn nth3(&self) -> ByteReader<'r> { + ByteReader::new_unchecked(&self.as_slice()[3..4]) + } + pub fn nth4(&self) -> ByteReader<'r> { + ByteReader::new_unchecked(&self.as_slice()[4..5]) + } + pub fn nth5(&self) -> ByteReader<'r> { + ByteReader::new_unchecked(&self.as_slice()[5..6]) + } + pub fn nth6(&self) -> ByteReader<'r> { + ByteReader::new_unchecked(&self.as_slice()[6..7]) + } + pub fn nth7(&self) -> ByteReader<'r> { + ByteReader::new_unchecked(&self.as_slice()[7..8]) + } + pub fn nth8(&self) -> ByteReader<'r> { + ByteReader::new_unchecked(&self.as_slice()[8..9]) + } + pub fn nth9(&self) -> ByteReader<'r> { + ByteReader::new_unchecked(&self.as_slice()[9..10]) + } + pub fn nth10(&self) -> ByteReader<'r> { + ByteReader::new_unchecked(&self.as_slice()[10..11]) + } + pub fn nth11(&self) -> ByteReader<'r> { + ByteReader::new_unchecked(&self.as_slice()[11..12]) + } + pub fn nth12(&self) -> ByteReader<'r> { + ByteReader::new_unchecked(&self.as_slice()[12..13]) + } + pub fn nth13(&self) -> ByteReader<'r> { + ByteReader::new_unchecked(&self.as_slice()[13..14]) + } + pub fn nth14(&self) -> ByteReader<'r> { + ByteReader::new_unchecked(&self.as_slice()[14..15]) + } + pub fn nth15(&self) -> ByteReader<'r> { + ByteReader::new_unchecked(&self.as_slice()[15..16]) + } + pub fn nth16(&self) -> ByteReader<'r> { + ByteReader::new_unchecked(&self.as_slice()[16..17]) + } + pub fn nth17(&self) -> ByteReader<'r> { + ByteReader::new_unchecked(&self.as_slice()[17..18]) + } + pub fn nth18(&self) -> ByteReader<'r> { + ByteReader::new_unchecked(&self.as_slice()[18..19]) + } + pub fn nth19(&self) -> ByteReader<'r> { + ByteReader::new_unchecked(&self.as_slice()[19..20]) + } + pub fn nth20(&self) -> ByteReader<'r> { + ByteReader::new_unchecked(&self.as_slice()[20..21]) + } + pub fn nth21(&self) -> ByteReader<'r> { + ByteReader::new_unchecked(&self.as_slice()[21..22]) + } + pub fn nth22(&self) -> ByteReader<'r> { + ByteReader::new_unchecked(&self.as_slice()[22..23]) + } + pub fn nth23(&self) -> ByteReader<'r> { + ByteReader::new_unchecked(&self.as_slice()[23..24]) + } + pub fn nth24(&self) -> ByteReader<'r> { + ByteReader::new_unchecked(&self.as_slice()[24..25]) + } + pub fn nth25(&self) -> ByteReader<'r> { + ByteReader::new_unchecked(&self.as_slice()[25..26]) + } + pub fn nth26(&self) -> ByteReader<'r> { + ByteReader::new_unchecked(&self.as_slice()[26..27]) + } + pub fn nth27(&self) -> ByteReader<'r> { + ByteReader::new_unchecked(&self.as_slice()[27..28]) + } + pub fn nth28(&self) -> ByteReader<'r> { + ByteReader::new_unchecked(&self.as_slice()[28..29]) + } + pub fn nth29(&self) -> ByteReader<'r> { + ByteReader::new_unchecked(&self.as_slice()[29..30]) + } + pub fn nth30(&self) -> ByteReader<'r> { + ByteReader::new_unchecked(&self.as_slice()[30..31]) + } + pub fn nth31(&self) -> ByteReader<'r> { + ByteReader::new_unchecked(&self.as_slice()[31..32]) + } + pub fn raw_data(&self) -> &'r [u8] { + self.as_slice() + } +} +impl<'r> molecule::prelude::Reader<'r> for HashReader<'r> { + type Entity = Hash; + const NAME: &'static str = "HashReader"; + fn to_entity(&self) -> Self::Entity { + Self::Entity::new_unchecked(self.as_slice().to_owned().into()) + } + fn new_unchecked(slice: &'r [u8]) -> Self { + HashReader(slice) + } + fn as_slice(&self) -> &'r [u8] { + self.0 + } + fn verify(slice: &[u8], _compatible: bool) -> molecule::error::VerificationResult<()> { + use molecule::verification_error as ve; + let slice_len = slice.len(); + if slice_len != Self::TOTAL_SIZE { + return ve!(Self, TotalSizeNotMatch, Self::TOTAL_SIZE, slice_len); + } + Ok(()) + } +} +pub struct HashBuilder(pub(crate) [Byte; 32]); +impl ::core::fmt::Debug for HashBuilder { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}({:?})", Self::NAME, &self.0[..]) + } +} +impl ::core::default::Default for HashBuilder { + fn default() -> Self { + HashBuilder([ + Byte::default(), + Byte::default(), + Byte::default(), + Byte::default(), + Byte::default(), + Byte::default(), + Byte::default(), + Byte::default(), + Byte::default(), + Byte::default(), + Byte::default(), + Byte::default(), + Byte::default(), + Byte::default(), + Byte::default(), + Byte::default(), + Byte::default(), + Byte::default(), + Byte::default(), + Byte::default(), + Byte::default(), + Byte::default(), + Byte::default(), + Byte::default(), + Byte::default(), + Byte::default(), + Byte::default(), + Byte::default(), + Byte::default(), + Byte::default(), + Byte::default(), + Byte::default(), + ]) + } +} +impl HashBuilder { + pub const TOTAL_SIZE: usize = 32; + pub const ITEM_SIZE: usize = 1; + pub const ITEM_COUNT: usize = 32; + pub fn set(mut self, v: [Byte; 32]) -> Self { + self.0 = v; + self + } + pub fn nth0(mut self, v: Byte) -> Self { + self.0[0] = v; + self + } + pub fn nth1(mut self, v: Byte) -> Self { + self.0[1] = v; + self + } + pub fn nth2(mut self, v: Byte) -> Self { + self.0[2] = v; + self + } + pub fn nth3(mut self, v: Byte) -> Self { + self.0[3] = v; + self + } + pub fn nth4(mut self, v: Byte) -> Self { + self.0[4] = v; + self + } + pub fn nth5(mut self, v: Byte) -> Self { + self.0[5] = v; + self + } + pub fn nth6(mut self, v: Byte) -> Self { + self.0[6] = v; + self + } + pub fn nth7(mut self, v: Byte) -> Self { + self.0[7] = v; + self + } + pub fn nth8(mut self, v: Byte) -> Self { + self.0[8] = v; + self + } + pub fn nth9(mut self, v: Byte) -> Self { + self.0[9] = v; + self + } + pub fn nth10(mut self, v: Byte) -> Self { + self.0[10] = v; + self + } + pub fn nth11(mut self, v: Byte) -> Self { + self.0[11] = v; + self + } + pub fn nth12(mut self, v: Byte) -> Self { + self.0[12] = v; + self + } + pub fn nth13(mut self, v: Byte) -> Self { + self.0[13] = v; + self + } + pub fn nth14(mut self, v: Byte) -> Self { + self.0[14] = v; + self + } + pub fn nth15(mut self, v: Byte) -> Self { + self.0[15] = v; + self + } + pub fn nth16(mut self, v: Byte) -> Self { + self.0[16] = v; + self + } + pub fn nth17(mut self, v: Byte) -> Self { + self.0[17] = v; + self + } + pub fn nth18(mut self, v: Byte) -> Self { + self.0[18] = v; + self + } + pub fn nth19(mut self, v: Byte) -> Self { + self.0[19] = v; + self + } + pub fn nth20(mut self, v: Byte) -> Self { + self.0[20] = v; + self + } + pub fn nth21(mut self, v: Byte) -> Self { + self.0[21] = v; + self + } + pub fn nth22(mut self, v: Byte) -> Self { + self.0[22] = v; + self + } + pub fn nth23(mut self, v: Byte) -> Self { + self.0[23] = v; + self + } + pub fn nth24(mut self, v: Byte) -> Self { + self.0[24] = v; + self + } + pub fn nth25(mut self, v: Byte) -> Self { + self.0[25] = v; + self + } + pub fn nth26(mut self, v: Byte) -> Self { + self.0[26] = v; + self + } + pub fn nth27(mut self, v: Byte) -> Self { + self.0[27] = v; + self + } + pub fn nth28(mut self, v: Byte) -> Self { + self.0[28] = v; + self + } + pub fn nth29(mut self, v: Byte) -> Self { + self.0[29] = v; + self + } + pub fn nth30(mut self, v: Byte) -> Self { + self.0[30] = v; + self + } + pub fn nth31(mut self, v: Byte) -> Self { + self.0[31] = v; + self + } +} +impl molecule::prelude::Builder for HashBuilder { + type Entity = Hash; + const NAME: &'static str = "HashBuilder"; + fn expected_length(&self) -> usize { + Self::TOTAL_SIZE + } + fn write(&self, writer: &mut W) -> molecule::io::Result<()> { + writer.write_all(self.0[0].as_slice())?; + writer.write_all(self.0[1].as_slice())?; + writer.write_all(self.0[2].as_slice())?; + writer.write_all(self.0[3].as_slice())?; + writer.write_all(self.0[4].as_slice())?; + writer.write_all(self.0[5].as_slice())?; + writer.write_all(self.0[6].as_slice())?; + writer.write_all(self.0[7].as_slice())?; + writer.write_all(self.0[8].as_slice())?; + writer.write_all(self.0[9].as_slice())?; + writer.write_all(self.0[10].as_slice())?; + writer.write_all(self.0[11].as_slice())?; + writer.write_all(self.0[12].as_slice())?; + writer.write_all(self.0[13].as_slice())?; + writer.write_all(self.0[14].as_slice())?; + writer.write_all(self.0[15].as_slice())?; + writer.write_all(self.0[16].as_slice())?; + writer.write_all(self.0[17].as_slice())?; + writer.write_all(self.0[18].as_slice())?; + writer.write_all(self.0[19].as_slice())?; + writer.write_all(self.0[20].as_slice())?; + writer.write_all(self.0[21].as_slice())?; + writer.write_all(self.0[22].as_slice())?; + writer.write_all(self.0[23].as_slice())?; + writer.write_all(self.0[24].as_slice())?; + writer.write_all(self.0[25].as_slice())?; + writer.write_all(self.0[26].as_slice())?; + writer.write_all(self.0[27].as_slice())?; + writer.write_all(self.0[28].as_slice())?; + writer.write_all(self.0[29].as_slice())?; + writer.write_all(self.0[30].as_slice())?; + writer.write_all(self.0[31].as_slice())?; + Ok(()) + } + fn build(&self) -> Self::Entity { + let mut inner = Vec::with_capacity(self.expected_length()); + self.write(&mut inner) + .unwrap_or_else(|_| panic!("{} build should be ok", Self::NAME)); + Hash::new_unchecked(inner.into()) + } +} +#[derive(Clone)] +pub struct String(molecule::bytes::Bytes); +impl ::core::fmt::LowerHex for String { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + if f.alternate() { + write!(f, "0x")?; + } + write!(f, "{}", hex_string(self.as_slice())) + } +} +impl ::core::fmt::Debug for String { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}({:#x})", Self::NAME, self) + } +} +impl ::core::fmt::Display for String { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + let raw_data = hex_string(&self.raw_data()); + write!(f, "{}(0x{})", Self::NAME, raw_data) + } +} +impl ::core::default::Default for String { + fn default() -> Self { + let v = molecule::bytes::Bytes::from_static(&Self::DEFAULT_VALUE); + String::new_unchecked(v) + } +} +impl String { + const DEFAULT_VALUE: [u8; 4] = [0, 0, 0, 0]; + pub const ITEM_SIZE: usize = 1; + pub fn total_size(&self) -> usize { + molecule::NUMBER_SIZE + Self::ITEM_SIZE * self.item_count() + } + pub fn item_count(&self) -> usize { + molecule::unpack_number(self.as_slice()) as usize + } + pub fn len(&self) -> usize { + self.item_count() + } + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + pub fn get(&self, idx: usize) -> Option { + if idx >= self.len() { + None + } else { + Some(self.get_unchecked(idx)) + } + } + pub fn get_unchecked(&self, idx: usize) -> Byte { + let start = molecule::NUMBER_SIZE + Self::ITEM_SIZE * idx; + let end = start + Self::ITEM_SIZE; + Byte::new_unchecked(self.0.slice(start..end)) + } + pub fn raw_data(&self) -> molecule::bytes::Bytes { + self.0.slice(molecule::NUMBER_SIZE..) + } + pub fn as_reader<'r>(&'r self) -> StringReader<'r> { + StringReader::new_unchecked(self.as_slice()) + } +} +impl molecule::prelude::Entity for String { + type Builder = StringBuilder; + const NAME: &'static str = "String"; + fn new_unchecked(data: molecule::bytes::Bytes) -> Self { + String(data) + } + fn as_bytes(&self) -> molecule::bytes::Bytes { + self.0.clone() + } + fn as_slice(&self) -> &[u8] { + &self.0[..] + } + fn from_slice(slice: &[u8]) -> molecule::error::VerificationResult { + StringReader::from_slice(slice).map(|reader| reader.to_entity()) + } + fn from_compatible_slice(slice: &[u8]) -> molecule::error::VerificationResult { + StringReader::from_compatible_slice(slice).map(|reader| reader.to_entity()) + } + fn new_builder() -> Self::Builder { + ::core::default::Default::default() + } + fn as_builder(self) -> Self::Builder { + Self::new_builder().extend(self.into_iter()) + } +} +#[derive(Clone, Copy)] +pub struct StringReader<'r>(&'r [u8]); +impl<'r> ::core::fmt::LowerHex for StringReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + if f.alternate() { + write!(f, "0x")?; + } + write!(f, "{}", hex_string(self.as_slice())) + } +} +impl<'r> ::core::fmt::Debug for StringReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}({:#x})", Self::NAME, self) + } +} +impl<'r> ::core::fmt::Display for StringReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + let raw_data = hex_string(&self.raw_data()); + write!(f, "{}(0x{})", Self::NAME, raw_data) + } +} +impl<'r> StringReader<'r> { + pub const ITEM_SIZE: usize = 1; + pub fn total_size(&self) -> usize { + molecule::NUMBER_SIZE + Self::ITEM_SIZE * self.item_count() + } + pub fn item_count(&self) -> usize { + molecule::unpack_number(self.as_slice()) as usize + } + pub fn len(&self) -> usize { + self.item_count() + } + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + pub fn get(&self, idx: usize) -> Option> { + if idx >= self.len() { + None + } else { + Some(self.get_unchecked(idx)) + } + } + pub fn get_unchecked(&self, idx: usize) -> ByteReader<'r> { + let start = molecule::NUMBER_SIZE + Self::ITEM_SIZE * idx; + let end = start + Self::ITEM_SIZE; + ByteReader::new_unchecked(&self.as_slice()[start..end]) + } + pub fn raw_data(&self) -> &'r [u8] { + &self.as_slice()[molecule::NUMBER_SIZE..] + } +} +impl<'r> molecule::prelude::Reader<'r> for StringReader<'r> { + type Entity = String; + const NAME: &'static str = "StringReader"; + fn to_entity(&self) -> Self::Entity { + Self::Entity::new_unchecked(self.as_slice().to_owned().into()) + } + fn new_unchecked(slice: &'r [u8]) -> Self { + StringReader(slice) + } + fn as_slice(&self) -> &'r [u8] { + self.0 + } + fn verify(slice: &[u8], _compatible: bool) -> molecule::error::VerificationResult<()> { + use molecule::verification_error as ve; + let slice_len = slice.len(); + if slice_len < molecule::NUMBER_SIZE { + return ve!(Self, HeaderIsBroken, molecule::NUMBER_SIZE, slice_len); + } + let item_count = molecule::unpack_number(slice) as usize; + if item_count == 0 { + if slice_len != molecule::NUMBER_SIZE { + return ve!(Self, TotalSizeNotMatch, molecule::NUMBER_SIZE, slice_len); + } + return Ok(()); + } + let total_size = molecule::NUMBER_SIZE + Self::ITEM_SIZE * item_count; + if slice_len != total_size { + return ve!(Self, TotalSizeNotMatch, total_size, slice_len); + } + Ok(()) + } +} +#[derive(Debug, Default)] +pub struct StringBuilder(pub(crate) Vec); +impl StringBuilder { + pub const ITEM_SIZE: usize = 1; + pub fn set(mut self, v: Vec) -> Self { + self.0 = v; + self + } + pub fn push(mut self, v: Byte) -> Self { + self.0.push(v); + self + } + pub fn extend>(mut self, iter: T) -> Self { + for elem in iter { + self.0.push(elem); + } + self + } + pub fn replace(&mut self, index: usize, v: Byte) -> Option { + self.0 + .get_mut(index) + .map(|item| ::core::mem::replace(item, v)) + } +} +impl molecule::prelude::Builder for StringBuilder { + type Entity = String; + const NAME: &'static str = "StringBuilder"; + fn expected_length(&self) -> usize { + molecule::NUMBER_SIZE + Self::ITEM_SIZE * self.0.len() + } + fn write(&self, writer: &mut W) -> molecule::io::Result<()> { + writer.write_all(&molecule::pack_number(self.0.len() as molecule::Number))?; + for inner in &self.0[..] { + writer.write_all(inner.as_slice())?; + } + Ok(()) + } + fn build(&self) -> Self::Entity { + let mut inner = Vec::with_capacity(self.expected_length()); + self.write(&mut inner) + .unwrap_or_else(|_| panic!("{} build should be ok", Self::NAME)); + String::new_unchecked(inner.into()) + } +} +pub struct StringIterator(String, usize, usize); +impl ::core::iter::Iterator for StringIterator { + type Item = Byte; + fn next(&mut self) -> Option { + if self.1 >= self.2 { + None + } else { + let ret = self.0.get_unchecked(self.1); + self.1 += 1; + Some(ret) + } + } +} +impl ::core::iter::ExactSizeIterator for StringIterator { + fn len(&self) -> usize { + self.2 - self.1 + } +} +impl ::core::iter::IntoIterator for String { + type Item = Byte; + type IntoIter = StringIterator; + fn into_iter(self) -> Self::IntoIter { + let len = self.len(); + StringIterator(self, 0, len) + } +} +#[derive(Clone)] +pub struct Uint32Opt(molecule::bytes::Bytes); +impl ::core::fmt::LowerHex for Uint32Opt { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + if f.alternate() { + write!(f, "0x")?; + } + write!(f, "{}", hex_string(self.as_slice())) + } +} +impl ::core::fmt::Debug for Uint32Opt { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}({:#x})", Self::NAME, self) + } +} +impl ::core::fmt::Display for Uint32Opt { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + if let Some(v) = self.to_opt() { + write!(f, "{}(Some({}))", Self::NAME, v) + } else { + write!(f, "{}(None)", Self::NAME) + } + } +} +impl ::core::default::Default for Uint32Opt { + fn default() -> Self { + let v = molecule::bytes::Bytes::from_static(&Self::DEFAULT_VALUE); + Uint32Opt::new_unchecked(v) + } +} +impl Uint32Opt { + const DEFAULT_VALUE: [u8; 0] = []; + pub fn is_none(&self) -> bool { + self.0.is_empty() + } + pub fn is_some(&self) -> bool { + !self.0.is_empty() + } + pub fn to_opt(&self) -> Option { + if self.is_none() { + None + } else { + Some(Uint32::new_unchecked(self.0.clone())) + } + } + pub fn as_reader<'r>(&'r self) -> Uint32OptReader<'r> { + Uint32OptReader::new_unchecked(self.as_slice()) + } +} +impl molecule::prelude::Entity for Uint32Opt { + type Builder = Uint32OptBuilder; + const NAME: &'static str = "Uint32Opt"; + fn new_unchecked(data: molecule::bytes::Bytes) -> Self { + Uint32Opt(data) + } + fn as_bytes(&self) -> molecule::bytes::Bytes { + self.0.clone() + } + fn as_slice(&self) -> &[u8] { + &self.0[..] + } + fn from_slice(slice: &[u8]) -> molecule::error::VerificationResult { + Uint32OptReader::from_slice(slice).map(|reader| reader.to_entity()) + } + fn from_compatible_slice(slice: &[u8]) -> molecule::error::VerificationResult { + Uint32OptReader::from_compatible_slice(slice).map(|reader| reader.to_entity()) + } + fn new_builder() -> Self::Builder { + ::core::default::Default::default() + } + fn as_builder(self) -> Self::Builder { + Self::new_builder().set(self.to_opt()) + } +} +#[derive(Clone, Copy)] +pub struct Uint32OptReader<'r>(&'r [u8]); +impl<'r> ::core::fmt::LowerHex for Uint32OptReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + if f.alternate() { + write!(f, "0x")?; + } + write!(f, "{}", hex_string(self.as_slice())) + } +} +impl<'r> ::core::fmt::Debug for Uint32OptReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}({:#x})", Self::NAME, self) + } +} +impl<'r> ::core::fmt::Display for Uint32OptReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + if let Some(v) = self.to_opt() { + write!(f, "{}(Some({}))", Self::NAME, v) + } else { + write!(f, "{}(None)", Self::NAME) + } + } +} +impl<'r> Uint32OptReader<'r> { + pub fn is_none(&self) -> bool { + self.0.is_empty() + } + pub fn is_some(&self) -> bool { + !self.0.is_empty() + } + pub fn to_opt(&self) -> Option> { + if self.is_none() { + None + } else { + Some(Uint32Reader::new_unchecked(self.as_slice())) + } + } +} +impl<'r> molecule::prelude::Reader<'r> for Uint32OptReader<'r> { + type Entity = Uint32Opt; + const NAME: &'static str = "Uint32OptReader"; + fn to_entity(&self) -> Self::Entity { + Self::Entity::new_unchecked(self.as_slice().to_owned().into()) + } + fn new_unchecked(slice: &'r [u8]) -> Self { + Uint32OptReader(slice) + } + fn as_slice(&self) -> &'r [u8] { + self.0 + } + fn verify(slice: &[u8], compatible: bool) -> molecule::error::VerificationResult<()> { + if !slice.is_empty() { + Uint32Reader::verify(&slice[..], compatible)?; + } + Ok(()) + } +} +#[derive(Debug, Default)] +pub struct Uint32OptBuilder(pub(crate) Option); +impl Uint32OptBuilder { + pub fn set(mut self, v: Option) -> Self { + self.0 = v; + self + } +} +impl molecule::prelude::Builder for Uint32OptBuilder { + type Entity = Uint32Opt; + const NAME: &'static str = "Uint32OptBuilder"; + fn expected_length(&self) -> usize { + self.0 + .as_ref() + .map(|ref inner| inner.as_slice().len()) + .unwrap_or(0) + } + fn write(&self, writer: &mut W) -> molecule::io::Result<()> { + self.0 + .as_ref() + .map(|ref inner| writer.write_all(inner.as_slice())) + .unwrap_or(Ok(())) + } + fn build(&self) -> Self::Entity { + let mut inner = Vec::with_capacity(self.expected_length()); + self.write(&mut inner) + .unwrap_or_else(|_| panic!("{} build should be ok", Self::NAME)); + Uint32Opt::new_unchecked(inner.into()) + } +} +#[derive(Clone)] +pub struct Action(molecule::bytes::Bytes); +impl ::core::fmt::LowerHex for Action { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + if f.alternate() { + write!(f, "0x")?; + } + write!(f, "{}", hex_string(self.as_slice())) + } +} +impl ::core::fmt::Debug for Action { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}({:#x})", Self::NAME, self) + } +} +impl ::core::fmt::Display for Action { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{} {{ ", Self::NAME)?; + write!(f, "{}: {}", "script_info_hash", self.script_info_hash())?; + write!(f, ", {}: {}", "script_hash", self.script_hash())?; + write!(f, ", {}: {}", "data", self.data())?; + let extra_count = self.count_extra_fields(); + if extra_count != 0 { + write!(f, ", .. ({} fields)", extra_count)?; + } + write!(f, " }}") + } +} +impl ::core::default::Default for Action { + fn default() -> Self { + let v = molecule::bytes::Bytes::from_static(&Self::DEFAULT_VALUE); + Action::new_unchecked(v) + } +} +impl Action { + const DEFAULT_VALUE: [u8; 84] = [ + 84, 0, 0, 0, 16, 0, 0, 0, 48, 0, 0, 0, 80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ]; + pub const FIELD_COUNT: usize = 3; + pub fn total_size(&self) -> usize { + molecule::unpack_number(self.as_slice()) as usize + } + pub fn field_count(&self) -> usize { + if self.total_size() == molecule::NUMBER_SIZE { + 0 + } else { + (molecule::unpack_number(&self.as_slice()[molecule::NUMBER_SIZE..]) as usize / 4) - 1 + } + } + pub fn count_extra_fields(&self) -> usize { + self.field_count() - Self::FIELD_COUNT + } + pub fn has_extra_fields(&self) -> bool { + Self::FIELD_COUNT != self.field_count() + } + pub fn script_info_hash(&self) -> Byte32 { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[4..]) as usize; + let end = molecule::unpack_number(&slice[8..]) as usize; + Byte32::new_unchecked(self.0.slice(start..end)) + } + pub fn script_hash(&self) -> Byte32 { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[8..]) as usize; + let end = molecule::unpack_number(&slice[12..]) as usize; + Byte32::new_unchecked(self.0.slice(start..end)) + } + pub fn data(&self) -> Bytes { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[12..]) as usize; + if self.has_extra_fields() { + let end = molecule::unpack_number(&slice[16..]) as usize; + Bytes::new_unchecked(self.0.slice(start..end)) + } else { + Bytes::new_unchecked(self.0.slice(start..)) + } + } + pub fn as_reader<'r>(&'r self) -> ActionReader<'r> { + ActionReader::new_unchecked(self.as_slice()) + } +} +impl molecule::prelude::Entity for Action { + type Builder = ActionBuilder; + const NAME: &'static str = "Action"; + fn new_unchecked(data: molecule::bytes::Bytes) -> Self { + Action(data) + } + fn as_bytes(&self) -> molecule::bytes::Bytes { + self.0.clone() + } + fn as_slice(&self) -> &[u8] { + &self.0[..] + } + fn from_slice(slice: &[u8]) -> molecule::error::VerificationResult { + ActionReader::from_slice(slice).map(|reader| reader.to_entity()) + } + fn from_compatible_slice(slice: &[u8]) -> molecule::error::VerificationResult { + ActionReader::from_compatible_slice(slice).map(|reader| reader.to_entity()) + } + fn new_builder() -> Self::Builder { + ::core::default::Default::default() + } + fn as_builder(self) -> Self::Builder { + Self::new_builder() + .script_info_hash(self.script_info_hash()) + .script_hash(self.script_hash()) + .data(self.data()) + } +} +#[derive(Clone, Copy)] +pub struct ActionReader<'r>(&'r [u8]); +impl<'r> ::core::fmt::LowerHex for ActionReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + if f.alternate() { + write!(f, "0x")?; + } + write!(f, "{}", hex_string(self.as_slice())) + } +} +impl<'r> ::core::fmt::Debug for ActionReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}({:#x})", Self::NAME, self) + } +} +impl<'r> ::core::fmt::Display for ActionReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{} {{ ", Self::NAME)?; + write!(f, "{}: {}", "script_info_hash", self.script_info_hash())?; + write!(f, ", {}: {}", "script_hash", self.script_hash())?; + write!(f, ", {}: {}", "data", self.data())?; + let extra_count = self.count_extra_fields(); + if extra_count != 0 { + write!(f, ", .. ({} fields)", extra_count)?; + } + write!(f, " }}") + } +} +impl<'r> ActionReader<'r> { + pub const FIELD_COUNT: usize = 3; + pub fn total_size(&self) -> usize { + molecule::unpack_number(self.as_slice()) as usize + } + pub fn field_count(&self) -> usize { + if self.total_size() == molecule::NUMBER_SIZE { + 0 + } else { + (molecule::unpack_number(&self.as_slice()[molecule::NUMBER_SIZE..]) as usize / 4) - 1 + } + } + pub fn count_extra_fields(&self) -> usize { + self.field_count() - Self::FIELD_COUNT + } + pub fn has_extra_fields(&self) -> bool { + Self::FIELD_COUNT != self.field_count() + } + pub fn script_info_hash(&self) -> Byte32Reader<'r> { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[4..]) as usize; + let end = molecule::unpack_number(&slice[8..]) as usize; + Byte32Reader::new_unchecked(&self.as_slice()[start..end]) + } + pub fn script_hash(&self) -> Byte32Reader<'r> { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[8..]) as usize; + let end = molecule::unpack_number(&slice[12..]) as usize; + Byte32Reader::new_unchecked(&self.as_slice()[start..end]) + } + pub fn data(&self) -> BytesReader<'r> { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[12..]) as usize; + if self.has_extra_fields() { + let end = molecule::unpack_number(&slice[16..]) as usize; + BytesReader::new_unchecked(&self.as_slice()[start..end]) + } else { + BytesReader::new_unchecked(&self.as_slice()[start..]) + } + } +} +impl<'r> molecule::prelude::Reader<'r> for ActionReader<'r> { + type Entity = Action; + const NAME: &'static str = "ActionReader"; + fn to_entity(&self) -> Self::Entity { + Self::Entity::new_unchecked(self.as_slice().to_owned().into()) + } + fn new_unchecked(slice: &'r [u8]) -> Self { + ActionReader(slice) + } + fn as_slice(&self) -> &'r [u8] { + self.0 + } + fn verify(slice: &[u8], compatible: bool) -> molecule::error::VerificationResult<()> { + use molecule::verification_error as ve; + let slice_len = slice.len(); + if slice_len < molecule::NUMBER_SIZE { + return ve!(Self, HeaderIsBroken, molecule::NUMBER_SIZE, slice_len); + } + let total_size = molecule::unpack_number(slice) as usize; + if slice_len != total_size { + return ve!(Self, TotalSizeNotMatch, total_size, slice_len); + } + if slice_len < molecule::NUMBER_SIZE * 2 { + return ve!(Self, HeaderIsBroken, molecule::NUMBER_SIZE * 2, slice_len); + } + let offset_first = molecule::unpack_number(&slice[molecule::NUMBER_SIZE..]) as usize; + if offset_first % molecule::NUMBER_SIZE != 0 || offset_first < molecule::NUMBER_SIZE * 2 { + return ve!(Self, OffsetsNotMatch); + } + if slice_len < offset_first { + return ve!(Self, HeaderIsBroken, offset_first, slice_len); + } + let field_count = offset_first / molecule::NUMBER_SIZE - 1; + if field_count < Self::FIELD_COUNT { + return ve!(Self, FieldCountNotMatch, Self::FIELD_COUNT, field_count); + } else if !compatible && field_count > Self::FIELD_COUNT { + return ve!(Self, FieldCountNotMatch, Self::FIELD_COUNT, field_count); + }; + let mut offsets: Vec = slice[molecule::NUMBER_SIZE..offset_first] + .chunks_exact(molecule::NUMBER_SIZE) + .map(|x| molecule::unpack_number(x) as usize) + .collect(); + offsets.push(total_size); + if offsets.windows(2).any(|i| i[0] > i[1]) { + return ve!(Self, OffsetsNotMatch); + } + Byte32Reader::verify(&slice[offsets[0]..offsets[1]], compatible)?; + Byte32Reader::verify(&slice[offsets[1]..offsets[2]], compatible)?; + BytesReader::verify(&slice[offsets[2]..offsets[3]], compatible)?; + Ok(()) + } +} +#[derive(Debug, Default)] +pub struct ActionBuilder { + pub(crate) script_info_hash: Byte32, + pub(crate) script_hash: Byte32, + pub(crate) data: Bytes, +} +impl ActionBuilder { + pub const FIELD_COUNT: usize = 3; + pub fn script_info_hash(mut self, v: Byte32) -> Self { + self.script_info_hash = v; + self + } + pub fn script_hash(mut self, v: Byte32) -> Self { + self.script_hash = v; + self + } + pub fn data(mut self, v: Bytes) -> Self { + self.data = v; + self + } +} +impl molecule::prelude::Builder for ActionBuilder { + type Entity = Action; + const NAME: &'static str = "ActionBuilder"; + fn expected_length(&self) -> usize { + molecule::NUMBER_SIZE * (Self::FIELD_COUNT + 1) + + self.script_info_hash.as_slice().len() + + self.script_hash.as_slice().len() + + self.data.as_slice().len() + } + fn write(&self, writer: &mut W) -> molecule::io::Result<()> { + let mut total_size = molecule::NUMBER_SIZE * (Self::FIELD_COUNT + 1); + let mut offsets = Vec::with_capacity(Self::FIELD_COUNT); + offsets.push(total_size); + total_size += self.script_info_hash.as_slice().len(); + offsets.push(total_size); + total_size += self.script_hash.as_slice().len(); + offsets.push(total_size); + total_size += self.data.as_slice().len(); + writer.write_all(&molecule::pack_number(total_size as molecule::Number))?; + for offset in offsets.into_iter() { + writer.write_all(&molecule::pack_number(offset as molecule::Number))?; + } + writer.write_all(self.script_info_hash.as_slice())?; + writer.write_all(self.script_hash.as_slice())?; + writer.write_all(self.data.as_slice())?; + Ok(()) + } + fn build(&self) -> Self::Entity { + let mut inner = Vec::with_capacity(self.expected_length()); + self.write(&mut inner) + .unwrap_or_else(|_| panic!("{} build should be ok", Self::NAME)); + Action::new_unchecked(inner.into()) + } +} +#[derive(Clone)] +pub struct ActionVec(molecule::bytes::Bytes); +impl ::core::fmt::LowerHex for ActionVec { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + if f.alternate() { + write!(f, "0x")?; + } + write!(f, "{}", hex_string(self.as_slice())) + } +} +impl ::core::fmt::Debug for ActionVec { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}({:#x})", Self::NAME, self) + } +} +impl ::core::fmt::Display for ActionVec { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{} [", Self::NAME)?; + for i in 0..self.len() { + if i == 0 { + write!(f, "{}", self.get_unchecked(i))?; + } else { + write!(f, ", {}", self.get_unchecked(i))?; + } + } + write!(f, "]") + } +} +impl ::core::default::Default for ActionVec { + fn default() -> Self { + let v = molecule::bytes::Bytes::from_static(&Self::DEFAULT_VALUE); + ActionVec::new_unchecked(v) + } +} +impl ActionVec { + const DEFAULT_VALUE: [u8; 4] = [4, 0, 0, 0]; + pub fn total_size(&self) -> usize { + molecule::unpack_number(self.as_slice()) as usize + } + pub fn item_count(&self) -> usize { + if self.total_size() == molecule::NUMBER_SIZE { + 0 + } else { + (molecule::unpack_number(&self.as_slice()[molecule::NUMBER_SIZE..]) as usize / 4) - 1 + } + } + pub fn len(&self) -> usize { + self.item_count() + } + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + pub fn get(&self, idx: usize) -> Option { + if idx >= self.len() { + None + } else { + Some(self.get_unchecked(idx)) + } + } + pub fn get_unchecked(&self, idx: usize) -> Action { + let slice = self.as_slice(); + let start_idx = molecule::NUMBER_SIZE * (1 + idx); + let start = molecule::unpack_number(&slice[start_idx..]) as usize; + if idx == self.len() - 1 { + Action::new_unchecked(self.0.slice(start..)) + } else { + let end_idx = start_idx + molecule::NUMBER_SIZE; + let end = molecule::unpack_number(&slice[end_idx..]) as usize; + Action::new_unchecked(self.0.slice(start..end)) + } + } + pub fn as_reader<'r>(&'r self) -> ActionVecReader<'r> { + ActionVecReader::new_unchecked(self.as_slice()) + } +} +impl molecule::prelude::Entity for ActionVec { + type Builder = ActionVecBuilder; + const NAME: &'static str = "ActionVec"; + fn new_unchecked(data: molecule::bytes::Bytes) -> Self { + ActionVec(data) + } + fn as_bytes(&self) -> molecule::bytes::Bytes { + self.0.clone() + } + fn as_slice(&self) -> &[u8] { + &self.0[..] + } + fn from_slice(slice: &[u8]) -> molecule::error::VerificationResult { + ActionVecReader::from_slice(slice).map(|reader| reader.to_entity()) + } + fn from_compatible_slice(slice: &[u8]) -> molecule::error::VerificationResult { + ActionVecReader::from_compatible_slice(slice).map(|reader| reader.to_entity()) + } + fn new_builder() -> Self::Builder { + ::core::default::Default::default() + } + fn as_builder(self) -> Self::Builder { + Self::new_builder().extend(self.into_iter()) + } +} +#[derive(Clone, Copy)] +pub struct ActionVecReader<'r>(&'r [u8]); +impl<'r> ::core::fmt::LowerHex for ActionVecReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + if f.alternate() { + write!(f, "0x")?; + } + write!(f, "{}", hex_string(self.as_slice())) + } +} +impl<'r> ::core::fmt::Debug for ActionVecReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}({:#x})", Self::NAME, self) + } +} +impl<'r> ::core::fmt::Display for ActionVecReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{} [", Self::NAME)?; + for i in 0..self.len() { + if i == 0 { + write!(f, "{}", self.get_unchecked(i))?; + } else { + write!(f, ", {}", self.get_unchecked(i))?; + } + } + write!(f, "]") + } +} +impl<'r> ActionVecReader<'r> { + pub fn total_size(&self) -> usize { + molecule::unpack_number(self.as_slice()) as usize + } + pub fn item_count(&self) -> usize { + if self.total_size() == molecule::NUMBER_SIZE { + 0 + } else { + (molecule::unpack_number(&self.as_slice()[molecule::NUMBER_SIZE..]) as usize / 4) - 1 + } + } + pub fn len(&self) -> usize { + self.item_count() + } + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + pub fn get(&self, idx: usize) -> Option> { + if idx >= self.len() { + None + } else { + Some(self.get_unchecked(idx)) + } + } + pub fn get_unchecked(&self, idx: usize) -> ActionReader<'r> { + let slice = self.as_slice(); + let start_idx = molecule::NUMBER_SIZE * (1 + idx); + let start = molecule::unpack_number(&slice[start_idx..]) as usize; + if idx == self.len() - 1 { + ActionReader::new_unchecked(&self.as_slice()[start..]) + } else { + let end_idx = start_idx + molecule::NUMBER_SIZE; + let end = molecule::unpack_number(&slice[end_idx..]) as usize; + ActionReader::new_unchecked(&self.as_slice()[start..end]) + } + } +} +impl<'r> molecule::prelude::Reader<'r> for ActionVecReader<'r> { + type Entity = ActionVec; + const NAME: &'static str = "ActionVecReader"; + fn to_entity(&self) -> Self::Entity { + Self::Entity::new_unchecked(self.as_slice().to_owned().into()) + } + fn new_unchecked(slice: &'r [u8]) -> Self { + ActionVecReader(slice) + } + fn as_slice(&self) -> &'r [u8] { + self.0 + } + fn verify(slice: &[u8], compatible: bool) -> molecule::error::VerificationResult<()> { + use molecule::verification_error as ve; + let slice_len = slice.len(); + if slice_len < molecule::NUMBER_SIZE { + return ve!(Self, HeaderIsBroken, molecule::NUMBER_SIZE, slice_len); + } + let total_size = molecule::unpack_number(slice) as usize; + if slice_len != total_size { + return ve!(Self, TotalSizeNotMatch, total_size, slice_len); + } + if slice_len == molecule::NUMBER_SIZE { + return Ok(()); + } + if slice_len < molecule::NUMBER_SIZE * 2 { + return ve!( + Self, + TotalSizeNotMatch, + molecule::NUMBER_SIZE * 2, + slice_len + ); + } + let offset_first = molecule::unpack_number(&slice[molecule::NUMBER_SIZE..]) as usize; + if offset_first % molecule::NUMBER_SIZE != 0 || offset_first < molecule::NUMBER_SIZE * 2 { + return ve!(Self, OffsetsNotMatch); + } + if slice_len < offset_first { + return ve!(Self, HeaderIsBroken, offset_first, slice_len); + } + let mut offsets: Vec = slice[molecule::NUMBER_SIZE..offset_first] + .chunks_exact(molecule::NUMBER_SIZE) + .map(|x| molecule::unpack_number(x) as usize) + .collect(); + offsets.push(total_size); + if offsets.windows(2).any(|i| i[0] > i[1]) { + return ve!(Self, OffsetsNotMatch); + } + for pair in offsets.windows(2) { + let start = pair[0]; + let end = pair[1]; + ActionReader::verify(&slice[start..end], compatible)?; + } + Ok(()) + } +} +#[derive(Debug, Default)] +pub struct ActionVecBuilder(pub(crate) Vec); +impl ActionVecBuilder { + pub fn set(mut self, v: Vec) -> Self { + self.0 = v; + self + } + pub fn push(mut self, v: Action) -> Self { + self.0.push(v); + self + } + pub fn extend>(mut self, iter: T) -> Self { + for elem in iter { + self.0.push(elem); + } + self + } + pub fn replace(&mut self, index: usize, v: Action) -> Option { + self.0 + .get_mut(index) + .map(|item| ::core::mem::replace(item, v)) + } +} +impl molecule::prelude::Builder for ActionVecBuilder { + type Entity = ActionVec; + const NAME: &'static str = "ActionVecBuilder"; + fn expected_length(&self) -> usize { + molecule::NUMBER_SIZE * (self.0.len() + 1) + + self + .0 + .iter() + .map(|inner| inner.as_slice().len()) + .sum::() + } + fn write(&self, writer: &mut W) -> molecule::io::Result<()> { + let item_count = self.0.len(); + if item_count == 0 { + writer.write_all(&molecule::pack_number( + molecule::NUMBER_SIZE as molecule::Number, + ))?; + } else { + let (total_size, offsets) = self.0.iter().fold( + ( + molecule::NUMBER_SIZE * (item_count + 1), + Vec::with_capacity(item_count), + ), + |(start, mut offsets), inner| { + offsets.push(start); + (start + inner.as_slice().len(), offsets) + }, + ); + writer.write_all(&molecule::pack_number(total_size as molecule::Number))?; + for offset in offsets.into_iter() { + writer.write_all(&molecule::pack_number(offset as molecule::Number))?; + } + for inner in self.0.iter() { + writer.write_all(inner.as_slice())?; + } + } + Ok(()) + } + fn build(&self) -> Self::Entity { + let mut inner = Vec::with_capacity(self.expected_length()); + self.write(&mut inner) + .unwrap_or_else(|_| panic!("{} build should be ok", Self::NAME)); + ActionVec::new_unchecked(inner.into()) + } +} +pub struct ActionVecIterator(ActionVec, usize, usize); +impl ::core::iter::Iterator for ActionVecIterator { + type Item = Action; + fn next(&mut self) -> Option { + if self.1 >= self.2 { + None + } else { + let ret = self.0.get_unchecked(self.1); + self.1 += 1; + Some(ret) + } + } +} +impl ::core::iter::ExactSizeIterator for ActionVecIterator { + fn len(&self) -> usize { + self.2 - self.1 + } +} +impl ::core::iter::IntoIterator for ActionVec { + type Item = Action; + type IntoIter = ActionVecIterator; + fn into_iter(self) -> Self::IntoIter { + let len = self.len(); + ActionVecIterator(self, 0, len) + } +} +impl<'r> ActionVecReader<'r> { + pub fn iter<'t>(&'t self) -> ActionVecReaderIterator<'t, 'r> { + ActionVecReaderIterator(&self, 0, self.len()) + } +} +pub struct ActionVecReaderIterator<'t, 'r>(&'t ActionVecReader<'r>, usize, usize); +impl<'t: 'r, 'r> ::core::iter::Iterator for ActionVecReaderIterator<'t, 'r> { + type Item = ActionReader<'t>; + fn next(&mut self) -> Option { + if self.1 >= self.2 { + None + } else { + let ret = self.0.get_unchecked(self.1); + self.1 += 1; + Some(ret) + } + } +} +impl<'t: 'r, 'r> ::core::iter::ExactSizeIterator for ActionVecReaderIterator<'t, 'r> { + fn len(&self) -> usize { + self.2 - self.1 + } +} +#[derive(Clone)] +pub struct Message(molecule::bytes::Bytes); +impl ::core::fmt::LowerHex for Message { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + if f.alternate() { + write!(f, "0x")?; + } + write!(f, "{}", hex_string(self.as_slice())) + } +} +impl ::core::fmt::Debug for Message { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}({:#x})", Self::NAME, self) + } +} +impl ::core::fmt::Display for Message { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{} {{ ", Self::NAME)?; + write!(f, "{}: {}", "actions", self.actions())?; + let extra_count = self.count_extra_fields(); + if extra_count != 0 { + write!(f, ", .. ({} fields)", extra_count)?; + } + write!(f, " }}") + } +} +impl ::core::default::Default for Message { + fn default() -> Self { + let v = molecule::bytes::Bytes::from_static(&Self::DEFAULT_VALUE); + Message::new_unchecked(v) + } +} +impl Message { + const DEFAULT_VALUE: [u8; 12] = [12, 0, 0, 0, 8, 0, 0, 0, 4, 0, 0, 0]; + pub const FIELD_COUNT: usize = 1; + pub fn total_size(&self) -> usize { + molecule::unpack_number(self.as_slice()) as usize + } + pub fn field_count(&self) -> usize { + if self.total_size() == molecule::NUMBER_SIZE { + 0 + } else { + (molecule::unpack_number(&self.as_slice()[molecule::NUMBER_SIZE..]) as usize / 4) - 1 + } + } + pub fn count_extra_fields(&self) -> usize { + self.field_count() - Self::FIELD_COUNT + } + pub fn has_extra_fields(&self) -> bool { + Self::FIELD_COUNT != self.field_count() + } + pub fn actions(&self) -> ActionVec { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[4..]) as usize; + if self.has_extra_fields() { + let end = molecule::unpack_number(&slice[8..]) as usize; + ActionVec::new_unchecked(self.0.slice(start..end)) + } else { + ActionVec::new_unchecked(self.0.slice(start..)) + } + } + pub fn as_reader<'r>(&'r self) -> MessageReader<'r> { + MessageReader::new_unchecked(self.as_slice()) + } +} +impl molecule::prelude::Entity for Message { + type Builder = MessageBuilder; + const NAME: &'static str = "Message"; + fn new_unchecked(data: molecule::bytes::Bytes) -> Self { + Message(data) + } + fn as_bytes(&self) -> molecule::bytes::Bytes { + self.0.clone() + } + fn as_slice(&self) -> &[u8] { + &self.0[..] + } + fn from_slice(slice: &[u8]) -> molecule::error::VerificationResult { + MessageReader::from_slice(slice).map(|reader| reader.to_entity()) + } + fn from_compatible_slice(slice: &[u8]) -> molecule::error::VerificationResult { + MessageReader::from_compatible_slice(slice).map(|reader| reader.to_entity()) + } + fn new_builder() -> Self::Builder { + ::core::default::Default::default() + } + fn as_builder(self) -> Self::Builder { + Self::new_builder().actions(self.actions()) + } +} +#[derive(Clone, Copy)] +pub struct MessageReader<'r>(&'r [u8]); +impl<'r> ::core::fmt::LowerHex for MessageReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + if f.alternate() { + write!(f, "0x")?; + } + write!(f, "{}", hex_string(self.as_slice())) + } +} +impl<'r> ::core::fmt::Debug for MessageReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}({:#x})", Self::NAME, self) + } +} +impl<'r> ::core::fmt::Display for MessageReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{} {{ ", Self::NAME)?; + write!(f, "{}: {}", "actions", self.actions())?; + let extra_count = self.count_extra_fields(); + if extra_count != 0 { + write!(f, ", .. ({} fields)", extra_count)?; + } + write!(f, " }}") + } +} +impl<'r> MessageReader<'r> { + pub const FIELD_COUNT: usize = 1; + pub fn total_size(&self) -> usize { + molecule::unpack_number(self.as_slice()) as usize + } + pub fn field_count(&self) -> usize { + if self.total_size() == molecule::NUMBER_SIZE { + 0 + } else { + (molecule::unpack_number(&self.as_slice()[molecule::NUMBER_SIZE..]) as usize / 4) - 1 + } + } + pub fn count_extra_fields(&self) -> usize { + self.field_count() - Self::FIELD_COUNT + } + pub fn has_extra_fields(&self) -> bool { + Self::FIELD_COUNT != self.field_count() + } + pub fn actions(&self) -> ActionVecReader<'r> { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[4..]) as usize; + if self.has_extra_fields() { + let end = molecule::unpack_number(&slice[8..]) as usize; + ActionVecReader::new_unchecked(&self.as_slice()[start..end]) + } else { + ActionVecReader::new_unchecked(&self.as_slice()[start..]) + } + } +} +impl<'r> molecule::prelude::Reader<'r> for MessageReader<'r> { + type Entity = Message; + const NAME: &'static str = "MessageReader"; + fn to_entity(&self) -> Self::Entity { + Self::Entity::new_unchecked(self.as_slice().to_owned().into()) + } + fn new_unchecked(slice: &'r [u8]) -> Self { + MessageReader(slice) + } + fn as_slice(&self) -> &'r [u8] { + self.0 + } + fn verify(slice: &[u8], compatible: bool) -> molecule::error::VerificationResult<()> { + use molecule::verification_error as ve; + let slice_len = slice.len(); + if slice_len < molecule::NUMBER_SIZE { + return ve!(Self, HeaderIsBroken, molecule::NUMBER_SIZE, slice_len); + } + let total_size = molecule::unpack_number(slice) as usize; + if slice_len != total_size { + return ve!(Self, TotalSizeNotMatch, total_size, slice_len); + } + if slice_len < molecule::NUMBER_SIZE * 2 { + return ve!(Self, HeaderIsBroken, molecule::NUMBER_SIZE * 2, slice_len); + } + let offset_first = molecule::unpack_number(&slice[molecule::NUMBER_SIZE..]) as usize; + if offset_first % molecule::NUMBER_SIZE != 0 || offset_first < molecule::NUMBER_SIZE * 2 { + return ve!(Self, OffsetsNotMatch); + } + if slice_len < offset_first { + return ve!(Self, HeaderIsBroken, offset_first, slice_len); + } + let field_count = offset_first / molecule::NUMBER_SIZE - 1; + if field_count < Self::FIELD_COUNT { + return ve!(Self, FieldCountNotMatch, Self::FIELD_COUNT, field_count); + } else if !compatible && field_count > Self::FIELD_COUNT { + return ve!(Self, FieldCountNotMatch, Self::FIELD_COUNT, field_count); + }; + let mut offsets: Vec = slice[molecule::NUMBER_SIZE..offset_first] + .chunks_exact(molecule::NUMBER_SIZE) + .map(|x| molecule::unpack_number(x) as usize) + .collect(); + offsets.push(total_size); + if offsets.windows(2).any(|i| i[0] > i[1]) { + return ve!(Self, OffsetsNotMatch); + } + ActionVecReader::verify(&slice[offsets[0]..offsets[1]], compatible)?; + Ok(()) + } +} +#[derive(Debug, Default)] +pub struct MessageBuilder { + pub(crate) actions: ActionVec, +} +impl MessageBuilder { + pub const FIELD_COUNT: usize = 1; + pub fn actions(mut self, v: ActionVec) -> Self { + self.actions = v; + self + } +} +impl molecule::prelude::Builder for MessageBuilder { + type Entity = Message; + const NAME: &'static str = "MessageBuilder"; + fn expected_length(&self) -> usize { + molecule::NUMBER_SIZE * (Self::FIELD_COUNT + 1) + self.actions.as_slice().len() + } + fn write(&self, writer: &mut W) -> molecule::io::Result<()> { + let mut total_size = molecule::NUMBER_SIZE * (Self::FIELD_COUNT + 1); + let mut offsets = Vec::with_capacity(Self::FIELD_COUNT); + offsets.push(total_size); + total_size += self.actions.as_slice().len(); + writer.write_all(&molecule::pack_number(total_size as molecule::Number))?; + for offset in offsets.into_iter() { + writer.write_all(&molecule::pack_number(offset as molecule::Number))?; + } + writer.write_all(self.actions.as_slice())?; + Ok(()) + } + fn build(&self) -> Self::Entity { + let mut inner = Vec::with_capacity(self.expected_length()); + self.write(&mut inner) + .unwrap_or_else(|_| panic!("{} build should be ok", Self::NAME)); + Message::new_unchecked(inner.into()) + } +} +#[derive(Clone)] +pub struct ScriptInfo(molecule::bytes::Bytes); +impl ::core::fmt::LowerHex for ScriptInfo { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + if f.alternate() { + write!(f, "0x")?; + } + write!(f, "{}", hex_string(self.as_slice())) + } +} +impl ::core::fmt::Debug for ScriptInfo { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}({:#x})", Self::NAME, self) + } +} +impl ::core::fmt::Display for ScriptInfo { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{} {{ ", Self::NAME)?; + write!(f, "{}: {}", "name", self.name())?; + write!(f, ", {}: {}", "url", self.url())?; + write!(f, ", {}: {}", "script_hash", self.script_hash())?; + write!(f, ", {}: {}", "schema", self.schema())?; + write!(f, ", {}: {}", "message_type", self.message_type())?; + let extra_count = self.count_extra_fields(); + if extra_count != 0 { + write!(f, ", .. ({} fields)", extra_count)?; + } + write!(f, " }}") + } +} +impl ::core::default::Default for ScriptInfo { + fn default() -> Self { + let v = molecule::bytes::Bytes::from_static(&Self::DEFAULT_VALUE); + ScriptInfo::new_unchecked(v) + } +} +impl ScriptInfo { + const DEFAULT_VALUE: [u8; 72] = [ + 72, 0, 0, 0, 24, 0, 0, 0, 28, 0, 0, 0, 32, 0, 0, 0, 64, 0, 0, 0, 68, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ]; + pub const FIELD_COUNT: usize = 5; + pub fn total_size(&self) -> usize { + molecule::unpack_number(self.as_slice()) as usize + } + pub fn field_count(&self) -> usize { + if self.total_size() == molecule::NUMBER_SIZE { + 0 + } else { + (molecule::unpack_number(&self.as_slice()[molecule::NUMBER_SIZE..]) as usize / 4) - 1 + } + } + pub fn count_extra_fields(&self) -> usize { + self.field_count() - Self::FIELD_COUNT + } + pub fn has_extra_fields(&self) -> bool { + Self::FIELD_COUNT != self.field_count() + } + pub fn name(&self) -> String { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[4..]) as usize; + let end = molecule::unpack_number(&slice[8..]) as usize; + String::new_unchecked(self.0.slice(start..end)) + } + pub fn url(&self) -> String { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[8..]) as usize; + let end = molecule::unpack_number(&slice[12..]) as usize; + String::new_unchecked(self.0.slice(start..end)) + } + pub fn script_hash(&self) -> Byte32 { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[12..]) as usize; + let end = molecule::unpack_number(&slice[16..]) as usize; + Byte32::new_unchecked(self.0.slice(start..end)) + } + pub fn schema(&self) -> String { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[16..]) as usize; + let end = molecule::unpack_number(&slice[20..]) as usize; + String::new_unchecked(self.0.slice(start..end)) + } + pub fn message_type(&self) -> String { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[20..]) as usize; + if self.has_extra_fields() { + let end = molecule::unpack_number(&slice[24..]) as usize; + String::new_unchecked(self.0.slice(start..end)) + } else { + String::new_unchecked(self.0.slice(start..)) + } + } + pub fn as_reader<'r>(&'r self) -> ScriptInfoReader<'r> { + ScriptInfoReader::new_unchecked(self.as_slice()) + } +} +impl molecule::prelude::Entity for ScriptInfo { + type Builder = ScriptInfoBuilder; + const NAME: &'static str = "ScriptInfo"; + fn new_unchecked(data: molecule::bytes::Bytes) -> Self { + ScriptInfo(data) + } + fn as_bytes(&self) -> molecule::bytes::Bytes { + self.0.clone() + } + fn as_slice(&self) -> &[u8] { + &self.0[..] + } + fn from_slice(slice: &[u8]) -> molecule::error::VerificationResult { + ScriptInfoReader::from_slice(slice).map(|reader| reader.to_entity()) + } + fn from_compatible_slice(slice: &[u8]) -> molecule::error::VerificationResult { + ScriptInfoReader::from_compatible_slice(slice).map(|reader| reader.to_entity()) + } + fn new_builder() -> Self::Builder { + ::core::default::Default::default() + } + fn as_builder(self) -> Self::Builder { + Self::new_builder() + .name(self.name()) + .url(self.url()) + .script_hash(self.script_hash()) + .schema(self.schema()) + .message_type(self.message_type()) + } +} +#[derive(Clone, Copy)] +pub struct ScriptInfoReader<'r>(&'r [u8]); +impl<'r> ::core::fmt::LowerHex for ScriptInfoReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + if f.alternate() { + write!(f, "0x")?; + } + write!(f, "{}", hex_string(self.as_slice())) + } +} +impl<'r> ::core::fmt::Debug for ScriptInfoReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}({:#x})", Self::NAME, self) + } +} +impl<'r> ::core::fmt::Display for ScriptInfoReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{} {{ ", Self::NAME)?; + write!(f, "{}: {}", "name", self.name())?; + write!(f, ", {}: {}", "url", self.url())?; + write!(f, ", {}: {}", "script_hash", self.script_hash())?; + write!(f, ", {}: {}", "schema", self.schema())?; + write!(f, ", {}: {}", "message_type", self.message_type())?; + let extra_count = self.count_extra_fields(); + if extra_count != 0 { + write!(f, ", .. ({} fields)", extra_count)?; + } + write!(f, " }}") + } +} +impl<'r> ScriptInfoReader<'r> { + pub const FIELD_COUNT: usize = 5; + pub fn total_size(&self) -> usize { + molecule::unpack_number(self.as_slice()) as usize + } + pub fn field_count(&self) -> usize { + if self.total_size() == molecule::NUMBER_SIZE { + 0 + } else { + (molecule::unpack_number(&self.as_slice()[molecule::NUMBER_SIZE..]) as usize / 4) - 1 + } + } + pub fn count_extra_fields(&self) -> usize { + self.field_count() - Self::FIELD_COUNT + } + pub fn has_extra_fields(&self) -> bool { + Self::FIELD_COUNT != self.field_count() + } + pub fn name(&self) -> StringReader<'r> { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[4..]) as usize; + let end = molecule::unpack_number(&slice[8..]) as usize; + StringReader::new_unchecked(&self.as_slice()[start..end]) + } + pub fn url(&self) -> StringReader<'r> { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[8..]) as usize; + let end = molecule::unpack_number(&slice[12..]) as usize; + StringReader::new_unchecked(&self.as_slice()[start..end]) + } + pub fn script_hash(&self) -> Byte32Reader<'r> { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[12..]) as usize; + let end = molecule::unpack_number(&slice[16..]) as usize; + Byte32Reader::new_unchecked(&self.as_slice()[start..end]) + } + pub fn schema(&self) -> StringReader<'r> { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[16..]) as usize; + let end = molecule::unpack_number(&slice[20..]) as usize; + StringReader::new_unchecked(&self.as_slice()[start..end]) + } + pub fn message_type(&self) -> StringReader<'r> { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[20..]) as usize; + if self.has_extra_fields() { + let end = molecule::unpack_number(&slice[24..]) as usize; + StringReader::new_unchecked(&self.as_slice()[start..end]) + } else { + StringReader::new_unchecked(&self.as_slice()[start..]) + } + } +} +impl<'r> molecule::prelude::Reader<'r> for ScriptInfoReader<'r> { + type Entity = ScriptInfo; + const NAME: &'static str = "ScriptInfoReader"; + fn to_entity(&self) -> Self::Entity { + Self::Entity::new_unchecked(self.as_slice().to_owned().into()) + } + fn new_unchecked(slice: &'r [u8]) -> Self { + ScriptInfoReader(slice) + } + fn as_slice(&self) -> &'r [u8] { + self.0 + } + fn verify(slice: &[u8], compatible: bool) -> molecule::error::VerificationResult<()> { + use molecule::verification_error as ve; + let slice_len = slice.len(); + if slice_len < molecule::NUMBER_SIZE { + return ve!(Self, HeaderIsBroken, molecule::NUMBER_SIZE, slice_len); + } + let total_size = molecule::unpack_number(slice) as usize; + if slice_len != total_size { + return ve!(Self, TotalSizeNotMatch, total_size, slice_len); + } + if slice_len < molecule::NUMBER_SIZE * 2 { + return ve!(Self, HeaderIsBroken, molecule::NUMBER_SIZE * 2, slice_len); + } + let offset_first = molecule::unpack_number(&slice[molecule::NUMBER_SIZE..]) as usize; + if offset_first % molecule::NUMBER_SIZE != 0 || offset_first < molecule::NUMBER_SIZE * 2 { + return ve!(Self, OffsetsNotMatch); + } + if slice_len < offset_first { + return ve!(Self, HeaderIsBroken, offset_first, slice_len); + } + let field_count = offset_first / molecule::NUMBER_SIZE - 1; + if field_count < Self::FIELD_COUNT { + return ve!(Self, FieldCountNotMatch, Self::FIELD_COUNT, field_count); + } else if !compatible && field_count > Self::FIELD_COUNT { + return ve!(Self, FieldCountNotMatch, Self::FIELD_COUNT, field_count); + }; + let mut offsets: Vec = slice[molecule::NUMBER_SIZE..offset_first] + .chunks_exact(molecule::NUMBER_SIZE) + .map(|x| molecule::unpack_number(x) as usize) + .collect(); + offsets.push(total_size); + if offsets.windows(2).any(|i| i[0] > i[1]) { + return ve!(Self, OffsetsNotMatch); + } + StringReader::verify(&slice[offsets[0]..offsets[1]], compatible)?; + StringReader::verify(&slice[offsets[1]..offsets[2]], compatible)?; + Byte32Reader::verify(&slice[offsets[2]..offsets[3]], compatible)?; + StringReader::verify(&slice[offsets[3]..offsets[4]], compatible)?; + StringReader::verify(&slice[offsets[4]..offsets[5]], compatible)?; + Ok(()) + } +} +#[derive(Debug, Default)] +pub struct ScriptInfoBuilder { + pub(crate) name: String, + pub(crate) url: String, + pub(crate) script_hash: Byte32, + pub(crate) schema: String, + pub(crate) message_type: String, +} +impl ScriptInfoBuilder { + pub const FIELD_COUNT: usize = 5; + pub fn name(mut self, v: String) -> Self { + self.name = v; + self + } + pub fn url(mut self, v: String) -> Self { + self.url = v; + self + } + pub fn script_hash(mut self, v: Byte32) -> Self { + self.script_hash = v; + self + } + pub fn schema(mut self, v: String) -> Self { + self.schema = v; + self + } + pub fn message_type(mut self, v: String) -> Self { + self.message_type = v; + self + } +} +impl molecule::prelude::Builder for ScriptInfoBuilder { + type Entity = ScriptInfo; + const NAME: &'static str = "ScriptInfoBuilder"; + fn expected_length(&self) -> usize { + molecule::NUMBER_SIZE * (Self::FIELD_COUNT + 1) + + self.name.as_slice().len() + + self.url.as_slice().len() + + self.script_hash.as_slice().len() + + self.schema.as_slice().len() + + self.message_type.as_slice().len() + } + fn write(&self, writer: &mut W) -> molecule::io::Result<()> { + let mut total_size = molecule::NUMBER_SIZE * (Self::FIELD_COUNT + 1); + let mut offsets = Vec::with_capacity(Self::FIELD_COUNT); + offsets.push(total_size); + total_size += self.name.as_slice().len(); + offsets.push(total_size); + total_size += self.url.as_slice().len(); + offsets.push(total_size); + total_size += self.script_hash.as_slice().len(); + offsets.push(total_size); + total_size += self.schema.as_slice().len(); + offsets.push(total_size); + total_size += self.message_type.as_slice().len(); + writer.write_all(&molecule::pack_number(total_size as molecule::Number))?; + for offset in offsets.into_iter() { + writer.write_all(&molecule::pack_number(offset as molecule::Number))?; + } + writer.write_all(self.name.as_slice())?; + writer.write_all(self.url.as_slice())?; + writer.write_all(self.script_hash.as_slice())?; + writer.write_all(self.schema.as_slice())?; + writer.write_all(self.message_type.as_slice())?; + Ok(()) + } + fn build(&self) -> Self::Entity { + let mut inner = Vec::with_capacity(self.expected_length()); + self.write(&mut inner) + .unwrap_or_else(|_| panic!("{} build should be ok", Self::NAME)); + ScriptInfo::new_unchecked(inner.into()) + } +} +#[derive(Clone)] +pub struct ScriptInfoVec(molecule::bytes::Bytes); +impl ::core::fmt::LowerHex for ScriptInfoVec { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + if f.alternate() { + write!(f, "0x")?; + } + write!(f, "{}", hex_string(self.as_slice())) + } +} +impl ::core::fmt::Debug for ScriptInfoVec { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}({:#x})", Self::NAME, self) + } +} +impl ::core::fmt::Display for ScriptInfoVec { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{} [", Self::NAME)?; + for i in 0..self.len() { + if i == 0 { + write!(f, "{}", self.get_unchecked(i))?; + } else { + write!(f, ", {}", self.get_unchecked(i))?; + } + } + write!(f, "]") + } +} +impl ::core::default::Default for ScriptInfoVec { + fn default() -> Self { + let v = molecule::bytes::Bytes::from_static(&Self::DEFAULT_VALUE); + ScriptInfoVec::new_unchecked(v) + } +} +impl ScriptInfoVec { + const DEFAULT_VALUE: [u8; 4] = [4, 0, 0, 0]; + pub fn total_size(&self) -> usize { + molecule::unpack_number(self.as_slice()) as usize + } + pub fn item_count(&self) -> usize { + if self.total_size() == molecule::NUMBER_SIZE { + 0 + } else { + (molecule::unpack_number(&self.as_slice()[molecule::NUMBER_SIZE..]) as usize / 4) - 1 + } + } + pub fn len(&self) -> usize { + self.item_count() + } + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + pub fn get(&self, idx: usize) -> Option { + if idx >= self.len() { + None + } else { + Some(self.get_unchecked(idx)) + } + } + pub fn get_unchecked(&self, idx: usize) -> ScriptInfo { + let slice = self.as_slice(); + let start_idx = molecule::NUMBER_SIZE * (1 + idx); + let start = molecule::unpack_number(&slice[start_idx..]) as usize; + if idx == self.len() - 1 { + ScriptInfo::new_unchecked(self.0.slice(start..)) + } else { + let end_idx = start_idx + molecule::NUMBER_SIZE; + let end = molecule::unpack_number(&slice[end_idx..]) as usize; + ScriptInfo::new_unchecked(self.0.slice(start..end)) + } + } + pub fn as_reader<'r>(&'r self) -> ScriptInfoVecReader<'r> { + ScriptInfoVecReader::new_unchecked(self.as_slice()) + } +} +impl molecule::prelude::Entity for ScriptInfoVec { + type Builder = ScriptInfoVecBuilder; + const NAME: &'static str = "ScriptInfoVec"; + fn new_unchecked(data: molecule::bytes::Bytes) -> Self { + ScriptInfoVec(data) + } + fn as_bytes(&self) -> molecule::bytes::Bytes { + self.0.clone() + } + fn as_slice(&self) -> &[u8] { + &self.0[..] + } + fn from_slice(slice: &[u8]) -> molecule::error::VerificationResult { + ScriptInfoVecReader::from_slice(slice).map(|reader| reader.to_entity()) + } + fn from_compatible_slice(slice: &[u8]) -> molecule::error::VerificationResult { + ScriptInfoVecReader::from_compatible_slice(slice).map(|reader| reader.to_entity()) + } + fn new_builder() -> Self::Builder { + ::core::default::Default::default() + } + fn as_builder(self) -> Self::Builder { + Self::new_builder().extend(self.into_iter()) + } +} +#[derive(Clone, Copy)] +pub struct ScriptInfoVecReader<'r>(&'r [u8]); +impl<'r> ::core::fmt::LowerHex for ScriptInfoVecReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + if f.alternate() { + write!(f, "0x")?; + } + write!(f, "{}", hex_string(self.as_slice())) + } +} +impl<'r> ::core::fmt::Debug for ScriptInfoVecReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}({:#x})", Self::NAME, self) + } +} +impl<'r> ::core::fmt::Display for ScriptInfoVecReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{} [", Self::NAME)?; + for i in 0..self.len() { + if i == 0 { + write!(f, "{}", self.get_unchecked(i))?; + } else { + write!(f, ", {}", self.get_unchecked(i))?; + } + } + write!(f, "]") + } +} +impl<'r> ScriptInfoVecReader<'r> { + pub fn total_size(&self) -> usize { + molecule::unpack_number(self.as_slice()) as usize + } + pub fn item_count(&self) -> usize { + if self.total_size() == molecule::NUMBER_SIZE { + 0 + } else { + (molecule::unpack_number(&self.as_slice()[molecule::NUMBER_SIZE..]) as usize / 4) - 1 + } + } + pub fn len(&self) -> usize { + self.item_count() + } + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + pub fn get(&self, idx: usize) -> Option> { + if idx >= self.len() { + None + } else { + Some(self.get_unchecked(idx)) + } + } + pub fn get_unchecked(&self, idx: usize) -> ScriptInfoReader<'r> { + let slice = self.as_slice(); + let start_idx = molecule::NUMBER_SIZE * (1 + idx); + let start = molecule::unpack_number(&slice[start_idx..]) as usize; + if idx == self.len() - 1 { + ScriptInfoReader::new_unchecked(&self.as_slice()[start..]) + } else { + let end_idx = start_idx + molecule::NUMBER_SIZE; + let end = molecule::unpack_number(&slice[end_idx..]) as usize; + ScriptInfoReader::new_unchecked(&self.as_slice()[start..end]) + } + } +} +impl<'r> molecule::prelude::Reader<'r> for ScriptInfoVecReader<'r> { + type Entity = ScriptInfoVec; + const NAME: &'static str = "ScriptInfoVecReader"; + fn to_entity(&self) -> Self::Entity { + Self::Entity::new_unchecked(self.as_slice().to_owned().into()) + } + fn new_unchecked(slice: &'r [u8]) -> Self { + ScriptInfoVecReader(slice) + } + fn as_slice(&self) -> &'r [u8] { + self.0 + } + fn verify(slice: &[u8], compatible: bool) -> molecule::error::VerificationResult<()> { + use molecule::verification_error as ve; + let slice_len = slice.len(); + if slice_len < molecule::NUMBER_SIZE { + return ve!(Self, HeaderIsBroken, molecule::NUMBER_SIZE, slice_len); + } + let total_size = molecule::unpack_number(slice) as usize; + if slice_len != total_size { + return ve!(Self, TotalSizeNotMatch, total_size, slice_len); + } + if slice_len == molecule::NUMBER_SIZE { + return Ok(()); + } + if slice_len < molecule::NUMBER_SIZE * 2 { + return ve!( + Self, + TotalSizeNotMatch, + molecule::NUMBER_SIZE * 2, + slice_len + ); + } + let offset_first = molecule::unpack_number(&slice[molecule::NUMBER_SIZE..]) as usize; + if offset_first % molecule::NUMBER_SIZE != 0 || offset_first < molecule::NUMBER_SIZE * 2 { + return ve!(Self, OffsetsNotMatch); + } + if slice_len < offset_first { + return ve!(Self, HeaderIsBroken, offset_first, slice_len); + } + let mut offsets: Vec = slice[molecule::NUMBER_SIZE..offset_first] + .chunks_exact(molecule::NUMBER_SIZE) + .map(|x| molecule::unpack_number(x) as usize) + .collect(); + offsets.push(total_size); + if offsets.windows(2).any(|i| i[0] > i[1]) { + return ve!(Self, OffsetsNotMatch); + } + for pair in offsets.windows(2) { + let start = pair[0]; + let end = pair[1]; + ScriptInfoReader::verify(&slice[start..end], compatible)?; + } + Ok(()) + } +} +#[derive(Debug, Default)] +pub struct ScriptInfoVecBuilder(pub(crate) Vec); +impl ScriptInfoVecBuilder { + pub fn set(mut self, v: Vec) -> Self { + self.0 = v; + self + } + pub fn push(mut self, v: ScriptInfo) -> Self { + self.0.push(v); + self + } + pub fn extend>(mut self, iter: T) -> Self { + for elem in iter { + self.0.push(elem); + } + self + } + pub fn replace(&mut self, index: usize, v: ScriptInfo) -> Option { + self.0 + .get_mut(index) + .map(|item| ::core::mem::replace(item, v)) + } +} +impl molecule::prelude::Builder for ScriptInfoVecBuilder { + type Entity = ScriptInfoVec; + const NAME: &'static str = "ScriptInfoVecBuilder"; + fn expected_length(&self) -> usize { + molecule::NUMBER_SIZE * (self.0.len() + 1) + + self + .0 + .iter() + .map(|inner| inner.as_slice().len()) + .sum::() + } + fn write(&self, writer: &mut W) -> molecule::io::Result<()> { + let item_count = self.0.len(); + if item_count == 0 { + writer.write_all(&molecule::pack_number( + molecule::NUMBER_SIZE as molecule::Number, + ))?; + } else { + let (total_size, offsets) = self.0.iter().fold( + ( + molecule::NUMBER_SIZE * (item_count + 1), + Vec::with_capacity(item_count), + ), + |(start, mut offsets), inner| { + offsets.push(start); + (start + inner.as_slice().len(), offsets) + }, + ); + writer.write_all(&molecule::pack_number(total_size as molecule::Number))?; + for offset in offsets.into_iter() { + writer.write_all(&molecule::pack_number(offset as molecule::Number))?; + } + for inner in self.0.iter() { + writer.write_all(inner.as_slice())?; + } + } + Ok(()) + } + fn build(&self) -> Self::Entity { + let mut inner = Vec::with_capacity(self.expected_length()); + self.write(&mut inner) + .unwrap_or_else(|_| panic!("{} build should be ok", Self::NAME)); + ScriptInfoVec::new_unchecked(inner.into()) + } +} +pub struct ScriptInfoVecIterator(ScriptInfoVec, usize, usize); +impl ::core::iter::Iterator for ScriptInfoVecIterator { + type Item = ScriptInfo; + fn next(&mut self) -> Option { + if self.1 >= self.2 { + None + } else { + let ret = self.0.get_unchecked(self.1); + self.1 += 1; + Some(ret) + } + } +} +impl ::core::iter::ExactSizeIterator for ScriptInfoVecIterator { + fn len(&self) -> usize { + self.2 - self.1 + } +} +impl ::core::iter::IntoIterator for ScriptInfoVec { + type Item = ScriptInfo; + type IntoIter = ScriptInfoVecIterator; + fn into_iter(self) -> Self::IntoIter { + let len = self.len(); + ScriptInfoVecIterator(self, 0, len) + } +} +impl<'r> ScriptInfoVecReader<'r> { + pub fn iter<'t>(&'t self) -> ScriptInfoVecReaderIterator<'t, 'r> { + ScriptInfoVecReaderIterator(&self, 0, self.len()) + } +} +pub struct ScriptInfoVecReaderIterator<'t, 'r>(&'t ScriptInfoVecReader<'r>, usize, usize); +impl<'t: 'r, 'r> ::core::iter::Iterator for ScriptInfoVecReaderIterator<'t, 'r> { + type Item = ScriptInfoReader<'t>; + fn next(&mut self) -> Option { + if self.1 >= self.2 { + None + } else { + let ret = self.0.get_unchecked(self.1); + self.1 += 1; + Some(ret) + } + } +} +impl<'t: 'r, 'r> ::core::iter::ExactSizeIterator for ScriptInfoVecReaderIterator<'t, 'r> { + fn len(&self) -> usize { + self.2 - self.1 + } +} +#[derive(Clone)] +pub struct ResolvedInputs(molecule::bytes::Bytes); +impl ::core::fmt::LowerHex for ResolvedInputs { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + if f.alternate() { + write!(f, "0x")?; + } + write!(f, "{}", hex_string(self.as_slice())) + } +} +impl ::core::fmt::Debug for ResolvedInputs { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}({:#x})", Self::NAME, self) + } +} +impl ::core::fmt::Display for ResolvedInputs { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{} {{ ", Self::NAME)?; + write!(f, "{}: {}", "outputs", self.outputs())?; + write!(f, ", {}: {}", "outputs_data", self.outputs_data())?; + let extra_count = self.count_extra_fields(); + if extra_count != 0 { + write!(f, ", .. ({} fields)", extra_count)?; + } + write!(f, " }}") + } +} +impl ::core::default::Default for ResolvedInputs { + fn default() -> Self { + let v = molecule::bytes::Bytes::from_static(&Self::DEFAULT_VALUE); + ResolvedInputs::new_unchecked(v) + } +} +impl ResolvedInputs { + const DEFAULT_VALUE: [u8; 20] = [ + 20, 0, 0, 0, 12, 0, 0, 0, 16, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, + ]; + pub const FIELD_COUNT: usize = 2; + pub fn total_size(&self) -> usize { + molecule::unpack_number(self.as_slice()) as usize + } + pub fn field_count(&self) -> usize { + if self.total_size() == molecule::NUMBER_SIZE { + 0 + } else { + (molecule::unpack_number(&self.as_slice()[molecule::NUMBER_SIZE..]) as usize / 4) - 1 + } + } + pub fn count_extra_fields(&self) -> usize { + self.field_count() - Self::FIELD_COUNT + } + pub fn has_extra_fields(&self) -> bool { + Self::FIELD_COUNT != self.field_count() + } + pub fn outputs(&self) -> CellOutputVec { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[4..]) as usize; + let end = molecule::unpack_number(&slice[8..]) as usize; + CellOutputVec::new_unchecked(self.0.slice(start..end)) + } + pub fn outputs_data(&self) -> BytesVec { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[8..]) as usize; + if self.has_extra_fields() { + let end = molecule::unpack_number(&slice[12..]) as usize; + BytesVec::new_unchecked(self.0.slice(start..end)) + } else { + BytesVec::new_unchecked(self.0.slice(start..)) + } + } + pub fn as_reader<'r>(&'r self) -> ResolvedInputsReader<'r> { + ResolvedInputsReader::new_unchecked(self.as_slice()) + } +} +impl molecule::prelude::Entity for ResolvedInputs { + type Builder = ResolvedInputsBuilder; + const NAME: &'static str = "ResolvedInputs"; + fn new_unchecked(data: molecule::bytes::Bytes) -> Self { + ResolvedInputs(data) + } + fn as_bytes(&self) -> molecule::bytes::Bytes { + self.0.clone() + } + fn as_slice(&self) -> &[u8] { + &self.0[..] + } + fn from_slice(slice: &[u8]) -> molecule::error::VerificationResult { + ResolvedInputsReader::from_slice(slice).map(|reader| reader.to_entity()) + } + fn from_compatible_slice(slice: &[u8]) -> molecule::error::VerificationResult { + ResolvedInputsReader::from_compatible_slice(slice).map(|reader| reader.to_entity()) + } + fn new_builder() -> Self::Builder { + ::core::default::Default::default() + } + fn as_builder(self) -> Self::Builder { + Self::new_builder() + .outputs(self.outputs()) + .outputs_data(self.outputs_data()) + } +} +#[derive(Clone, Copy)] +pub struct ResolvedInputsReader<'r>(&'r [u8]); +impl<'r> ::core::fmt::LowerHex for ResolvedInputsReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + if f.alternate() { + write!(f, "0x")?; + } + write!(f, "{}", hex_string(self.as_slice())) + } +} +impl<'r> ::core::fmt::Debug for ResolvedInputsReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}({:#x})", Self::NAME, self) + } +} +impl<'r> ::core::fmt::Display for ResolvedInputsReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{} {{ ", Self::NAME)?; + write!(f, "{}: {}", "outputs", self.outputs())?; + write!(f, ", {}: {}", "outputs_data", self.outputs_data())?; + let extra_count = self.count_extra_fields(); + if extra_count != 0 { + write!(f, ", .. ({} fields)", extra_count)?; + } + write!(f, " }}") + } +} +impl<'r> ResolvedInputsReader<'r> { + pub const FIELD_COUNT: usize = 2; + pub fn total_size(&self) -> usize { + molecule::unpack_number(self.as_slice()) as usize + } + pub fn field_count(&self) -> usize { + if self.total_size() == molecule::NUMBER_SIZE { + 0 + } else { + (molecule::unpack_number(&self.as_slice()[molecule::NUMBER_SIZE..]) as usize / 4) - 1 + } + } + pub fn count_extra_fields(&self) -> usize { + self.field_count() - Self::FIELD_COUNT + } + pub fn has_extra_fields(&self) -> bool { + Self::FIELD_COUNT != self.field_count() + } + pub fn outputs(&self) -> CellOutputVecReader<'r> { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[4..]) as usize; + let end = molecule::unpack_number(&slice[8..]) as usize; + CellOutputVecReader::new_unchecked(&self.as_slice()[start..end]) + } + pub fn outputs_data(&self) -> BytesVecReader<'r> { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[8..]) as usize; + if self.has_extra_fields() { + let end = molecule::unpack_number(&slice[12..]) as usize; + BytesVecReader::new_unchecked(&self.as_slice()[start..end]) + } else { + BytesVecReader::new_unchecked(&self.as_slice()[start..]) + } + } +} +impl<'r> molecule::prelude::Reader<'r> for ResolvedInputsReader<'r> { + type Entity = ResolvedInputs; + const NAME: &'static str = "ResolvedInputsReader"; + fn to_entity(&self) -> Self::Entity { + Self::Entity::new_unchecked(self.as_slice().to_owned().into()) + } + fn new_unchecked(slice: &'r [u8]) -> Self { + ResolvedInputsReader(slice) + } + fn as_slice(&self) -> &'r [u8] { + self.0 + } + fn verify(slice: &[u8], compatible: bool) -> molecule::error::VerificationResult<()> { + use molecule::verification_error as ve; + let slice_len = slice.len(); + if slice_len < molecule::NUMBER_SIZE { + return ve!(Self, HeaderIsBroken, molecule::NUMBER_SIZE, slice_len); + } + let total_size = molecule::unpack_number(slice) as usize; + if slice_len != total_size { + return ve!(Self, TotalSizeNotMatch, total_size, slice_len); + } + if slice_len < molecule::NUMBER_SIZE * 2 { + return ve!(Self, HeaderIsBroken, molecule::NUMBER_SIZE * 2, slice_len); + } + let offset_first = molecule::unpack_number(&slice[molecule::NUMBER_SIZE..]) as usize; + if offset_first % molecule::NUMBER_SIZE != 0 || offset_first < molecule::NUMBER_SIZE * 2 { + return ve!(Self, OffsetsNotMatch); + } + if slice_len < offset_first { + return ve!(Self, HeaderIsBroken, offset_first, slice_len); + } + let field_count = offset_first / molecule::NUMBER_SIZE - 1; + if field_count < Self::FIELD_COUNT { + return ve!(Self, FieldCountNotMatch, Self::FIELD_COUNT, field_count); + } else if !compatible && field_count > Self::FIELD_COUNT { + return ve!(Self, FieldCountNotMatch, Self::FIELD_COUNT, field_count); + }; + let mut offsets: Vec = slice[molecule::NUMBER_SIZE..offset_first] + .chunks_exact(molecule::NUMBER_SIZE) + .map(|x| molecule::unpack_number(x) as usize) + .collect(); + offsets.push(total_size); + if offsets.windows(2).any(|i| i[0] > i[1]) { + return ve!(Self, OffsetsNotMatch); + } + CellOutputVecReader::verify(&slice[offsets[0]..offsets[1]], compatible)?; + BytesVecReader::verify(&slice[offsets[1]..offsets[2]], compatible)?; + Ok(()) + } +} +#[derive(Debug, Default)] +pub struct ResolvedInputsBuilder { + pub(crate) outputs: CellOutputVec, + pub(crate) outputs_data: BytesVec, +} +impl ResolvedInputsBuilder { + pub const FIELD_COUNT: usize = 2; + pub fn outputs(mut self, v: CellOutputVec) -> Self { + self.outputs = v; + self + } + pub fn outputs_data(mut self, v: BytesVec) -> Self { + self.outputs_data = v; + self + } +} +impl molecule::prelude::Builder for ResolvedInputsBuilder { + type Entity = ResolvedInputs; + const NAME: &'static str = "ResolvedInputsBuilder"; + fn expected_length(&self) -> usize { + molecule::NUMBER_SIZE * (Self::FIELD_COUNT + 1) + + self.outputs.as_slice().len() + + self.outputs_data.as_slice().len() + } + fn write(&self, writer: &mut W) -> molecule::io::Result<()> { + let mut total_size = molecule::NUMBER_SIZE * (Self::FIELD_COUNT + 1); + let mut offsets = Vec::with_capacity(Self::FIELD_COUNT); + offsets.push(total_size); + total_size += self.outputs.as_slice().len(); + offsets.push(total_size); + total_size += self.outputs_data.as_slice().len(); + writer.write_all(&molecule::pack_number(total_size as molecule::Number))?; + for offset in offsets.into_iter() { + writer.write_all(&molecule::pack_number(offset as molecule::Number))?; + } + writer.write_all(self.outputs.as_slice())?; + writer.write_all(self.outputs_data.as_slice())?; + Ok(()) + } + fn build(&self) -> Self::Entity { + let mut inner = Vec::with_capacity(self.expected_length()); + self.write(&mut inner) + .unwrap_or_else(|_| panic!("{} build should be ok", Self::NAME)); + ResolvedInputs::new_unchecked(inner.into()) + } +} +#[derive(Clone)] +pub struct BuildingPacketV1(molecule::bytes::Bytes); +impl ::core::fmt::LowerHex for BuildingPacketV1 { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + if f.alternate() { + write!(f, "0x")?; + } + write!(f, "{}", hex_string(self.as_slice())) + } +} +impl ::core::fmt::Debug for BuildingPacketV1 { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}({:#x})", Self::NAME, self) + } +} +impl ::core::fmt::Display for BuildingPacketV1 { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{} {{ ", Self::NAME)?; + write!(f, "{}: {}", "message", self.message())?; + write!(f, ", {}: {}", "payload", self.payload())?; + write!(f, ", {}: {}", "resolved_inputs", self.resolved_inputs())?; + write!(f, ", {}: {}", "change_output", self.change_output())?; + write!(f, ", {}: {}", "script_infos", self.script_infos())?; + write!(f, ", {}: {}", "lock_actions", self.lock_actions())?; + let extra_count = self.count_extra_fields(); + if extra_count != 0 { + write!(f, ", .. ({} fields)", extra_count)?; + } + write!(f, " }}") + } +} +impl ::core::default::Default for BuildingPacketV1 { + fn default() -> Self { + let v = molecule::bytes::Bytes::from_static(&Self::DEFAULT_VALUE); + BuildingPacketV1::new_unchecked(v) + } +} +impl BuildingPacketV1 { + const DEFAULT_VALUE: [u8; 136] = [ + 136, 0, 0, 0, 28, 0, 0, 0, 40, 0, 0, 0, 108, 0, 0, 0, 128, 0, 0, 0, 128, 0, 0, 0, 132, 0, + 0, 0, 12, 0, 0, 0, 8, 0, 0, 0, 4, 0, 0, 0, 68, 0, 0, 0, 12, 0, 0, 0, 64, 0, 0, 0, 52, 0, 0, + 0, 28, 0, 0, 0, 32, 0, 0, 0, 36, 0, 0, 0, 40, 0, 0, 0, 44, 0, 0, 0, 48, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 20, 0, 0, 0, 12, + 0, 0, 0, 16, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, + ]; + pub const FIELD_COUNT: usize = 6; + pub fn total_size(&self) -> usize { + molecule::unpack_number(self.as_slice()) as usize + } + pub fn field_count(&self) -> usize { + if self.total_size() == molecule::NUMBER_SIZE { + 0 + } else { + (molecule::unpack_number(&self.as_slice()[molecule::NUMBER_SIZE..]) as usize / 4) - 1 + } + } + pub fn count_extra_fields(&self) -> usize { + self.field_count() - Self::FIELD_COUNT + } + pub fn has_extra_fields(&self) -> bool { + Self::FIELD_COUNT != self.field_count() + } + pub fn message(&self) -> Message { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[4..]) as usize; + let end = molecule::unpack_number(&slice[8..]) as usize; + Message::new_unchecked(self.0.slice(start..end)) + } + pub fn payload(&self) -> Transaction { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[8..]) as usize; + let end = molecule::unpack_number(&slice[12..]) as usize; + Transaction::new_unchecked(self.0.slice(start..end)) + } + pub fn resolved_inputs(&self) -> ResolvedInputs { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[12..]) as usize; + let end = molecule::unpack_number(&slice[16..]) as usize; + ResolvedInputs::new_unchecked(self.0.slice(start..end)) + } + pub fn change_output(&self) -> Uint32Opt { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[16..]) as usize; + let end = molecule::unpack_number(&slice[20..]) as usize; + Uint32Opt::new_unchecked(self.0.slice(start..end)) + } + pub fn script_infos(&self) -> ScriptInfoVec { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[20..]) as usize; + let end = molecule::unpack_number(&slice[24..]) as usize; + ScriptInfoVec::new_unchecked(self.0.slice(start..end)) + } + pub fn lock_actions(&self) -> ActionVec { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[24..]) as usize; + if self.has_extra_fields() { + let end = molecule::unpack_number(&slice[28..]) as usize; + ActionVec::new_unchecked(self.0.slice(start..end)) + } else { + ActionVec::new_unchecked(self.0.slice(start..)) + } + } + pub fn as_reader<'r>(&'r self) -> BuildingPacketV1Reader<'r> { + BuildingPacketV1Reader::new_unchecked(self.as_slice()) + } +} +impl molecule::prelude::Entity for BuildingPacketV1 { + type Builder = BuildingPacketV1Builder; + const NAME: &'static str = "BuildingPacketV1"; + fn new_unchecked(data: molecule::bytes::Bytes) -> Self { + BuildingPacketV1(data) + } + fn as_bytes(&self) -> molecule::bytes::Bytes { + self.0.clone() + } + fn as_slice(&self) -> &[u8] { + &self.0[..] + } + fn from_slice(slice: &[u8]) -> molecule::error::VerificationResult { + BuildingPacketV1Reader::from_slice(slice).map(|reader| reader.to_entity()) + } + fn from_compatible_slice(slice: &[u8]) -> molecule::error::VerificationResult { + BuildingPacketV1Reader::from_compatible_slice(slice).map(|reader| reader.to_entity()) + } + fn new_builder() -> Self::Builder { + ::core::default::Default::default() + } + fn as_builder(self) -> Self::Builder { + Self::new_builder() + .message(self.message()) + .payload(self.payload()) + .resolved_inputs(self.resolved_inputs()) + .change_output(self.change_output()) + .script_infos(self.script_infos()) + .lock_actions(self.lock_actions()) + } +} +#[derive(Clone, Copy)] +pub struct BuildingPacketV1Reader<'r>(&'r [u8]); +impl<'r> ::core::fmt::LowerHex for BuildingPacketV1Reader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + if f.alternate() { + write!(f, "0x")?; + } + write!(f, "{}", hex_string(self.as_slice())) + } +} +impl<'r> ::core::fmt::Debug for BuildingPacketV1Reader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}({:#x})", Self::NAME, self) + } +} +impl<'r> ::core::fmt::Display for BuildingPacketV1Reader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{} {{ ", Self::NAME)?; + write!(f, "{}: {}", "message", self.message())?; + write!(f, ", {}: {}", "payload", self.payload())?; + write!(f, ", {}: {}", "resolved_inputs", self.resolved_inputs())?; + write!(f, ", {}: {}", "change_output", self.change_output())?; + write!(f, ", {}: {}", "script_infos", self.script_infos())?; + write!(f, ", {}: {}", "lock_actions", self.lock_actions())?; + let extra_count = self.count_extra_fields(); + if extra_count != 0 { + write!(f, ", .. ({} fields)", extra_count)?; + } + write!(f, " }}") + } +} +impl<'r> BuildingPacketV1Reader<'r> { + pub const FIELD_COUNT: usize = 6; + pub fn total_size(&self) -> usize { + molecule::unpack_number(self.as_slice()) as usize + } + pub fn field_count(&self) -> usize { + if self.total_size() == molecule::NUMBER_SIZE { + 0 + } else { + (molecule::unpack_number(&self.as_slice()[molecule::NUMBER_SIZE..]) as usize / 4) - 1 + } + } + pub fn count_extra_fields(&self) -> usize { + self.field_count() - Self::FIELD_COUNT + } + pub fn has_extra_fields(&self) -> bool { + Self::FIELD_COUNT != self.field_count() + } + pub fn message(&self) -> MessageReader<'r> { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[4..]) as usize; + let end = molecule::unpack_number(&slice[8..]) as usize; + MessageReader::new_unchecked(&self.as_slice()[start..end]) + } + pub fn payload(&self) -> TransactionReader<'r> { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[8..]) as usize; + let end = molecule::unpack_number(&slice[12..]) as usize; + TransactionReader::new_unchecked(&self.as_slice()[start..end]) + } + pub fn resolved_inputs(&self) -> ResolvedInputsReader<'r> { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[12..]) as usize; + let end = molecule::unpack_number(&slice[16..]) as usize; + ResolvedInputsReader::new_unchecked(&self.as_slice()[start..end]) + } + pub fn change_output(&self) -> Uint32OptReader<'r> { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[16..]) as usize; + let end = molecule::unpack_number(&slice[20..]) as usize; + Uint32OptReader::new_unchecked(&self.as_slice()[start..end]) + } + pub fn script_infos(&self) -> ScriptInfoVecReader<'r> { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[20..]) as usize; + let end = molecule::unpack_number(&slice[24..]) as usize; + ScriptInfoVecReader::new_unchecked(&self.as_slice()[start..end]) + } + pub fn lock_actions(&self) -> ActionVecReader<'r> { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[24..]) as usize; + if self.has_extra_fields() { + let end = molecule::unpack_number(&slice[28..]) as usize; + ActionVecReader::new_unchecked(&self.as_slice()[start..end]) + } else { + ActionVecReader::new_unchecked(&self.as_slice()[start..]) + } + } +} +impl<'r> molecule::prelude::Reader<'r> for BuildingPacketV1Reader<'r> { + type Entity = BuildingPacketV1; + const NAME: &'static str = "BuildingPacketV1Reader"; + fn to_entity(&self) -> Self::Entity { + Self::Entity::new_unchecked(self.as_slice().to_owned().into()) + } + fn new_unchecked(slice: &'r [u8]) -> Self { + BuildingPacketV1Reader(slice) + } + fn as_slice(&self) -> &'r [u8] { + self.0 + } + fn verify(slice: &[u8], compatible: bool) -> molecule::error::VerificationResult<()> { + use molecule::verification_error as ve; + let slice_len = slice.len(); + if slice_len < molecule::NUMBER_SIZE { + return ve!(Self, HeaderIsBroken, molecule::NUMBER_SIZE, slice_len); + } + let total_size = molecule::unpack_number(slice) as usize; + if slice_len != total_size { + return ve!(Self, TotalSizeNotMatch, total_size, slice_len); + } + if slice_len < molecule::NUMBER_SIZE * 2 { + return ve!(Self, HeaderIsBroken, molecule::NUMBER_SIZE * 2, slice_len); + } + let offset_first = molecule::unpack_number(&slice[molecule::NUMBER_SIZE..]) as usize; + if offset_first % molecule::NUMBER_SIZE != 0 || offset_first < molecule::NUMBER_SIZE * 2 { + return ve!(Self, OffsetsNotMatch); + } + if slice_len < offset_first { + return ve!(Self, HeaderIsBroken, offset_first, slice_len); + } + let field_count = offset_first / molecule::NUMBER_SIZE - 1; + if field_count < Self::FIELD_COUNT { + return ve!(Self, FieldCountNotMatch, Self::FIELD_COUNT, field_count); + } else if !compatible && field_count > Self::FIELD_COUNT { + return ve!(Self, FieldCountNotMatch, Self::FIELD_COUNT, field_count); + }; + let mut offsets: Vec = slice[molecule::NUMBER_SIZE..offset_first] + .chunks_exact(molecule::NUMBER_SIZE) + .map(|x| molecule::unpack_number(x) as usize) + .collect(); + offsets.push(total_size); + if offsets.windows(2).any(|i| i[0] > i[1]) { + return ve!(Self, OffsetsNotMatch); + } + MessageReader::verify(&slice[offsets[0]..offsets[1]], compatible)?; + TransactionReader::verify(&slice[offsets[1]..offsets[2]], compatible)?; + ResolvedInputsReader::verify(&slice[offsets[2]..offsets[3]], compatible)?; + Uint32OptReader::verify(&slice[offsets[3]..offsets[4]], compatible)?; + ScriptInfoVecReader::verify(&slice[offsets[4]..offsets[5]], compatible)?; + ActionVecReader::verify(&slice[offsets[5]..offsets[6]], compatible)?; + Ok(()) + } +} +#[derive(Debug, Default)] +pub struct BuildingPacketV1Builder { + pub(crate) message: Message, + pub(crate) payload: Transaction, + pub(crate) resolved_inputs: ResolvedInputs, + pub(crate) change_output: Uint32Opt, + pub(crate) script_infos: ScriptInfoVec, + pub(crate) lock_actions: ActionVec, +} +impl BuildingPacketV1Builder { + pub const FIELD_COUNT: usize = 6; + pub fn message(mut self, v: Message) -> Self { + self.message = v; + self + } + pub fn payload(mut self, v: Transaction) -> Self { + self.payload = v; + self + } + pub fn resolved_inputs(mut self, v: ResolvedInputs) -> Self { + self.resolved_inputs = v; + self + } + pub fn change_output(mut self, v: Uint32Opt) -> Self { + self.change_output = v; + self + } + pub fn script_infos(mut self, v: ScriptInfoVec) -> Self { + self.script_infos = v; + self + } + pub fn lock_actions(mut self, v: ActionVec) -> Self { + self.lock_actions = v; + self + } +} +impl molecule::prelude::Builder for BuildingPacketV1Builder { + type Entity = BuildingPacketV1; + const NAME: &'static str = "BuildingPacketV1Builder"; + fn expected_length(&self) -> usize { + molecule::NUMBER_SIZE * (Self::FIELD_COUNT + 1) + + self.message.as_slice().len() + + self.payload.as_slice().len() + + self.resolved_inputs.as_slice().len() + + self.change_output.as_slice().len() + + self.script_infos.as_slice().len() + + self.lock_actions.as_slice().len() + } + fn write(&self, writer: &mut W) -> molecule::io::Result<()> { + let mut total_size = molecule::NUMBER_SIZE * (Self::FIELD_COUNT + 1); + let mut offsets = Vec::with_capacity(Self::FIELD_COUNT); + offsets.push(total_size); + total_size += self.message.as_slice().len(); + offsets.push(total_size); + total_size += self.payload.as_slice().len(); + offsets.push(total_size); + total_size += self.resolved_inputs.as_slice().len(); + offsets.push(total_size); + total_size += self.change_output.as_slice().len(); + offsets.push(total_size); + total_size += self.script_infos.as_slice().len(); + offsets.push(total_size); + total_size += self.lock_actions.as_slice().len(); + writer.write_all(&molecule::pack_number(total_size as molecule::Number))?; + for offset in offsets.into_iter() { + writer.write_all(&molecule::pack_number(offset as molecule::Number))?; + } + writer.write_all(self.message.as_slice())?; + writer.write_all(self.payload.as_slice())?; + writer.write_all(self.resolved_inputs.as_slice())?; + writer.write_all(self.change_output.as_slice())?; + writer.write_all(self.script_infos.as_slice())?; + writer.write_all(self.lock_actions.as_slice())?; + Ok(()) + } + fn build(&self) -> Self::Entity { + let mut inner = Vec::with_capacity(self.expected_length()); + self.write(&mut inner) + .unwrap_or_else(|_| panic!("{} build should be ok", Self::NAME)); + BuildingPacketV1::new_unchecked(inner.into()) + } +} +#[derive(Clone)] +pub struct BuildingPacket(molecule::bytes::Bytes); +impl ::core::fmt::LowerHex for BuildingPacket { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + if f.alternate() { + write!(f, "0x")?; + } + write!(f, "{}", hex_string(self.as_slice())) + } +} +impl ::core::fmt::Debug for BuildingPacket { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}({:#x})", Self::NAME, self) + } +} +impl ::core::fmt::Display for BuildingPacket { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}(", Self::NAME)?; + self.to_enum().display_inner(f)?; + write!(f, ")") + } +} +impl ::core::default::Default for BuildingPacket { + fn default() -> Self { + let v = molecule::bytes::Bytes::from_static(&Self::DEFAULT_VALUE); + BuildingPacket::new_unchecked(v) + } +} +impl BuildingPacket { + const DEFAULT_VALUE: [u8; 140] = [ + 0, 0, 0, 0, 136, 0, 0, 0, 28, 0, 0, 0, 40, 0, 0, 0, 108, 0, 0, 0, 128, 0, 0, 0, 128, 0, 0, + 0, 132, 0, 0, 0, 12, 0, 0, 0, 8, 0, 0, 0, 4, 0, 0, 0, 68, 0, 0, 0, 12, 0, 0, 0, 64, 0, 0, + 0, 52, 0, 0, 0, 28, 0, 0, 0, 32, 0, 0, 0, 36, 0, 0, 0, 40, 0, 0, 0, 44, 0, 0, 0, 48, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 20, + 0, 0, 0, 12, 0, 0, 0, 16, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, + ]; + pub const ITEMS_COUNT: usize = 1; + pub fn item_id(&self) -> molecule::Number { + molecule::unpack_number(self.as_slice()) + } + pub fn to_enum(&self) -> BuildingPacketUnion { + let inner = self.0.slice(molecule::NUMBER_SIZE..); + match self.item_id() { + 0 => BuildingPacketV1::new_unchecked(inner).into(), + _ => panic!("{}: invalid data", Self::NAME), + } + } + pub fn as_reader<'r>(&'r self) -> BuildingPacketReader<'r> { + BuildingPacketReader::new_unchecked(self.as_slice()) + } +} +impl molecule::prelude::Entity for BuildingPacket { + type Builder = BuildingPacketBuilder; + const NAME: &'static str = "BuildingPacket"; + fn new_unchecked(data: molecule::bytes::Bytes) -> Self { + BuildingPacket(data) + } + fn as_bytes(&self) -> molecule::bytes::Bytes { + self.0.clone() + } + fn as_slice(&self) -> &[u8] { + &self.0[..] + } + fn from_slice(slice: &[u8]) -> molecule::error::VerificationResult { + BuildingPacketReader::from_slice(slice).map(|reader| reader.to_entity()) + } + fn from_compatible_slice(slice: &[u8]) -> molecule::error::VerificationResult { + BuildingPacketReader::from_compatible_slice(slice).map(|reader| reader.to_entity()) + } + fn new_builder() -> Self::Builder { + ::core::default::Default::default() + } + fn as_builder(self) -> Self::Builder { + Self::new_builder().set(self.to_enum()) + } +} +#[derive(Clone, Copy)] +pub struct BuildingPacketReader<'r>(&'r [u8]); +impl<'r> ::core::fmt::LowerHex for BuildingPacketReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + if f.alternate() { + write!(f, "0x")?; + } + write!(f, "{}", hex_string(self.as_slice())) + } +} +impl<'r> ::core::fmt::Debug for BuildingPacketReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}({:#x})", Self::NAME, self) + } +} +impl<'r> ::core::fmt::Display for BuildingPacketReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}(", Self::NAME)?; + self.to_enum().display_inner(f)?; + write!(f, ")") + } +} +impl<'r> BuildingPacketReader<'r> { + pub const ITEMS_COUNT: usize = 1; + pub fn item_id(&self) -> molecule::Number { + molecule::unpack_number(self.as_slice()) + } + pub fn to_enum(&self) -> BuildingPacketUnionReader<'r> { + let inner = &self.as_slice()[molecule::NUMBER_SIZE..]; + match self.item_id() { + 0 => BuildingPacketV1Reader::new_unchecked(inner).into(), + _ => panic!("{}: invalid data", Self::NAME), + } + } +} +impl<'r> molecule::prelude::Reader<'r> for BuildingPacketReader<'r> { + type Entity = BuildingPacket; + const NAME: &'static str = "BuildingPacketReader"; + fn to_entity(&self) -> Self::Entity { + Self::Entity::new_unchecked(self.as_slice().to_owned().into()) + } + fn new_unchecked(slice: &'r [u8]) -> Self { + BuildingPacketReader(slice) + } + fn as_slice(&self) -> &'r [u8] { + self.0 + } + fn verify(slice: &[u8], compatible: bool) -> molecule::error::VerificationResult<()> { + use molecule::verification_error as ve; + let slice_len = slice.len(); + if slice_len < molecule::NUMBER_SIZE { + return ve!(Self, HeaderIsBroken, molecule::NUMBER_SIZE, slice_len); + } + let item_id = molecule::unpack_number(slice); + let inner_slice = &slice[molecule::NUMBER_SIZE..]; + match item_id { + 0 => BuildingPacketV1Reader::verify(inner_slice, compatible), + _ => ve!(Self, UnknownItem, Self::ITEMS_COUNT, item_id), + }?; + Ok(()) + } +} +#[derive(Debug, Default)] +pub struct BuildingPacketBuilder(pub(crate) BuildingPacketUnion); +impl BuildingPacketBuilder { + pub const ITEMS_COUNT: usize = 1; + pub fn set(mut self, v: I) -> Self + where + I: ::core::convert::Into, + { + self.0 = v.into(); + self + } +} +impl molecule::prelude::Builder for BuildingPacketBuilder { + type Entity = BuildingPacket; + const NAME: &'static str = "BuildingPacketBuilder"; + fn expected_length(&self) -> usize { + molecule::NUMBER_SIZE + self.0.as_slice().len() + } + fn write(&self, writer: &mut W) -> molecule::io::Result<()> { + writer.write_all(&molecule::pack_number(self.0.item_id()))?; + writer.write_all(self.0.as_slice()) + } + fn build(&self) -> Self::Entity { + let mut inner = Vec::with_capacity(self.expected_length()); + self.write(&mut inner) + .unwrap_or_else(|_| panic!("{} build should be ok", Self::NAME)); + BuildingPacket::new_unchecked(inner.into()) + } +} +#[derive(Debug, Clone)] +pub enum BuildingPacketUnion { + BuildingPacketV1(BuildingPacketV1), +} +#[derive(Debug, Clone, Copy)] +pub enum BuildingPacketUnionReader<'r> { + BuildingPacketV1(BuildingPacketV1Reader<'r>), +} +impl ::core::default::Default for BuildingPacketUnion { + fn default() -> Self { + BuildingPacketUnion::BuildingPacketV1(::core::default::Default::default()) + } +} +impl ::core::fmt::Display for BuildingPacketUnion { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + match self { + BuildingPacketUnion::BuildingPacketV1(ref item) => { + write!(f, "{}::{}({})", Self::NAME, BuildingPacketV1::NAME, item) + } + } + } +} +impl<'r> ::core::fmt::Display for BuildingPacketUnionReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + match self { + BuildingPacketUnionReader::BuildingPacketV1(ref item) => { + write!(f, "{}::{}({})", Self::NAME, BuildingPacketV1::NAME, item) + } + } + } +} +impl BuildingPacketUnion { + pub(crate) fn display_inner(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + match self { + BuildingPacketUnion::BuildingPacketV1(ref item) => write!(f, "{}", item), + } + } +} +impl<'r> BuildingPacketUnionReader<'r> { + pub(crate) fn display_inner(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + match self { + BuildingPacketUnionReader::BuildingPacketV1(ref item) => write!(f, "{}", item), + } + } +} +impl ::core::convert::From for BuildingPacketUnion { + fn from(item: BuildingPacketV1) -> Self { + BuildingPacketUnion::BuildingPacketV1(item) + } +} +impl<'r> ::core::convert::From> for BuildingPacketUnionReader<'r> { + fn from(item: BuildingPacketV1Reader<'r>) -> Self { + BuildingPacketUnionReader::BuildingPacketV1(item) + } +} +impl BuildingPacketUnion { + pub const NAME: &'static str = "BuildingPacketUnion"; + pub fn as_bytes(&self) -> molecule::bytes::Bytes { + match self { + BuildingPacketUnion::BuildingPacketV1(item) => item.as_bytes(), + } + } + pub fn as_slice(&self) -> &[u8] { + match self { + BuildingPacketUnion::BuildingPacketV1(item) => item.as_slice(), + } + } + pub fn item_id(&self) -> molecule::Number { + match self { + BuildingPacketUnion::BuildingPacketV1(_) => 0, + } + } + pub fn item_name(&self) -> &str { + match self { + BuildingPacketUnion::BuildingPacketV1(_) => "BuildingPacketV1", + } + } + pub fn as_reader<'r>(&'r self) -> BuildingPacketUnionReader<'r> { + match self { + BuildingPacketUnion::BuildingPacketV1(item) => item.as_reader().into(), + } + } +} +impl<'r> BuildingPacketUnionReader<'r> { + pub const NAME: &'r str = "BuildingPacketUnionReader"; + pub fn as_slice(&self) -> &'r [u8] { + match self { + BuildingPacketUnionReader::BuildingPacketV1(item) => item.as_slice(), + } + } + pub fn item_id(&self) -> molecule::Number { + match self { + BuildingPacketUnionReader::BuildingPacketV1(_) => 0, + } + } + pub fn item_name(&self) -> &str { + match self { + BuildingPacketUnionReader::BuildingPacketV1(_) => "BuildingPacketV1", + } + } +} +#[derive(Clone)] +pub struct SighashAll(molecule::bytes::Bytes); +impl ::core::fmt::LowerHex for SighashAll { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + if f.alternate() { + write!(f, "0x")?; + } + write!(f, "{}", hex_string(self.as_slice())) + } +} +impl ::core::fmt::Debug for SighashAll { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}({:#x})", Self::NAME, self) + } +} +impl ::core::fmt::Display for SighashAll { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{} {{ ", Self::NAME)?; + write!(f, "{}: {}", "message", self.message())?; + write!(f, ", {}: {}", "seal", self.seal())?; + let extra_count = self.count_extra_fields(); + if extra_count != 0 { + write!(f, ", .. ({} fields)", extra_count)?; + } + write!(f, " }}") + } +} +impl ::core::default::Default for SighashAll { + fn default() -> Self { + let v = molecule::bytes::Bytes::from_static(&Self::DEFAULT_VALUE); + SighashAll::new_unchecked(v) + } +} +impl SighashAll { + const DEFAULT_VALUE: [u8; 28] = [ + 28, 0, 0, 0, 12, 0, 0, 0, 24, 0, 0, 0, 12, 0, 0, 0, 8, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, + ]; + pub const FIELD_COUNT: usize = 2; + pub fn total_size(&self) -> usize { + molecule::unpack_number(self.as_slice()) as usize + } + pub fn field_count(&self) -> usize { + if self.total_size() == molecule::NUMBER_SIZE { + 0 + } else { + (molecule::unpack_number(&self.as_slice()[molecule::NUMBER_SIZE..]) as usize / 4) - 1 + } + } + pub fn count_extra_fields(&self) -> usize { + self.field_count() - Self::FIELD_COUNT + } + pub fn has_extra_fields(&self) -> bool { + Self::FIELD_COUNT != self.field_count() + } + pub fn message(&self) -> Message { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[4..]) as usize; + let end = molecule::unpack_number(&slice[8..]) as usize; + Message::new_unchecked(self.0.slice(start..end)) + } + pub fn seal(&self) -> Bytes { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[8..]) as usize; + if self.has_extra_fields() { + let end = molecule::unpack_number(&slice[12..]) as usize; + Bytes::new_unchecked(self.0.slice(start..end)) + } else { + Bytes::new_unchecked(self.0.slice(start..)) + } + } + pub fn as_reader<'r>(&'r self) -> SighashAllReader<'r> { + SighashAllReader::new_unchecked(self.as_slice()) + } +} +impl molecule::prelude::Entity for SighashAll { + type Builder = SighashAllBuilder; + const NAME: &'static str = "SighashAll"; + fn new_unchecked(data: molecule::bytes::Bytes) -> Self { + SighashAll(data) + } + fn as_bytes(&self) -> molecule::bytes::Bytes { + self.0.clone() + } + fn as_slice(&self) -> &[u8] { + &self.0[..] + } + fn from_slice(slice: &[u8]) -> molecule::error::VerificationResult { + SighashAllReader::from_slice(slice).map(|reader| reader.to_entity()) + } + fn from_compatible_slice(slice: &[u8]) -> molecule::error::VerificationResult { + SighashAllReader::from_compatible_slice(slice).map(|reader| reader.to_entity()) + } + fn new_builder() -> Self::Builder { + ::core::default::Default::default() + } + fn as_builder(self) -> Self::Builder { + Self::new_builder() + .message(self.message()) + .seal(self.seal()) + } +} +#[derive(Clone, Copy)] +pub struct SighashAllReader<'r>(&'r [u8]); +impl<'r> ::core::fmt::LowerHex for SighashAllReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + if f.alternate() { + write!(f, "0x")?; + } + write!(f, "{}", hex_string(self.as_slice())) + } +} +impl<'r> ::core::fmt::Debug for SighashAllReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}({:#x})", Self::NAME, self) + } +} +impl<'r> ::core::fmt::Display for SighashAllReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{} {{ ", Self::NAME)?; + write!(f, "{}: {}", "message", self.message())?; + write!(f, ", {}: {}", "seal", self.seal())?; + let extra_count = self.count_extra_fields(); + if extra_count != 0 { + write!(f, ", .. ({} fields)", extra_count)?; + } + write!(f, " }}") + } +} +impl<'r> SighashAllReader<'r> { + pub const FIELD_COUNT: usize = 2; + pub fn total_size(&self) -> usize { + molecule::unpack_number(self.as_slice()) as usize + } + pub fn field_count(&self) -> usize { + if self.total_size() == molecule::NUMBER_SIZE { + 0 + } else { + (molecule::unpack_number(&self.as_slice()[molecule::NUMBER_SIZE..]) as usize / 4) - 1 + } + } + pub fn count_extra_fields(&self) -> usize { + self.field_count() - Self::FIELD_COUNT + } + pub fn has_extra_fields(&self) -> bool { + Self::FIELD_COUNT != self.field_count() + } + pub fn message(&self) -> MessageReader<'r> { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[4..]) as usize; + let end = molecule::unpack_number(&slice[8..]) as usize; + MessageReader::new_unchecked(&self.as_slice()[start..end]) + } + pub fn seal(&self) -> BytesReader<'r> { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[8..]) as usize; + if self.has_extra_fields() { + let end = molecule::unpack_number(&slice[12..]) as usize; + BytesReader::new_unchecked(&self.as_slice()[start..end]) + } else { + BytesReader::new_unchecked(&self.as_slice()[start..]) + } + } +} +impl<'r> molecule::prelude::Reader<'r> for SighashAllReader<'r> { + type Entity = SighashAll; + const NAME: &'static str = "SighashAllReader"; + fn to_entity(&self) -> Self::Entity { + Self::Entity::new_unchecked(self.as_slice().to_owned().into()) + } + fn new_unchecked(slice: &'r [u8]) -> Self { + SighashAllReader(slice) + } + fn as_slice(&self) -> &'r [u8] { + self.0 + } + fn verify(slice: &[u8], compatible: bool) -> molecule::error::VerificationResult<()> { + use molecule::verification_error as ve; + let slice_len = slice.len(); + if slice_len < molecule::NUMBER_SIZE { + return ve!(Self, HeaderIsBroken, molecule::NUMBER_SIZE, slice_len); + } + let total_size = molecule::unpack_number(slice) as usize; + if slice_len != total_size { + return ve!(Self, TotalSizeNotMatch, total_size, slice_len); + } + if slice_len < molecule::NUMBER_SIZE * 2 { + return ve!(Self, HeaderIsBroken, molecule::NUMBER_SIZE * 2, slice_len); + } + let offset_first = molecule::unpack_number(&slice[molecule::NUMBER_SIZE..]) as usize; + if offset_first % molecule::NUMBER_SIZE != 0 || offset_first < molecule::NUMBER_SIZE * 2 { + return ve!(Self, OffsetsNotMatch); + } + if slice_len < offset_first { + return ve!(Self, HeaderIsBroken, offset_first, slice_len); + } + let field_count = offset_first / molecule::NUMBER_SIZE - 1; + if field_count < Self::FIELD_COUNT { + return ve!(Self, FieldCountNotMatch, Self::FIELD_COUNT, field_count); + } else if !compatible && field_count > Self::FIELD_COUNT { + return ve!(Self, FieldCountNotMatch, Self::FIELD_COUNT, field_count); + }; + let mut offsets: Vec = slice[molecule::NUMBER_SIZE..offset_first] + .chunks_exact(molecule::NUMBER_SIZE) + .map(|x| molecule::unpack_number(x) as usize) + .collect(); + offsets.push(total_size); + if offsets.windows(2).any(|i| i[0] > i[1]) { + return ve!(Self, OffsetsNotMatch); + } + MessageReader::verify(&slice[offsets[0]..offsets[1]], compatible)?; + BytesReader::verify(&slice[offsets[1]..offsets[2]], compatible)?; + Ok(()) + } +} +#[derive(Debug, Default)] +pub struct SighashAllBuilder { + pub(crate) message: Message, + pub(crate) seal: Bytes, +} +impl SighashAllBuilder { + pub const FIELD_COUNT: usize = 2; + pub fn message(mut self, v: Message) -> Self { + self.message = v; + self + } + pub fn seal(mut self, v: Bytes) -> Self { + self.seal = v; + self + } +} +impl molecule::prelude::Builder for SighashAllBuilder { + type Entity = SighashAll; + const NAME: &'static str = "SighashAllBuilder"; + fn expected_length(&self) -> usize { + molecule::NUMBER_SIZE * (Self::FIELD_COUNT + 1) + + self.message.as_slice().len() + + self.seal.as_slice().len() + } + fn write(&self, writer: &mut W) -> molecule::io::Result<()> { + let mut total_size = molecule::NUMBER_SIZE * (Self::FIELD_COUNT + 1); + let mut offsets = Vec::with_capacity(Self::FIELD_COUNT); + offsets.push(total_size); + total_size += self.message.as_slice().len(); + offsets.push(total_size); + total_size += self.seal.as_slice().len(); + writer.write_all(&molecule::pack_number(total_size as molecule::Number))?; + for offset in offsets.into_iter() { + writer.write_all(&molecule::pack_number(offset as molecule::Number))?; + } + writer.write_all(self.message.as_slice())?; + writer.write_all(self.seal.as_slice())?; + Ok(()) + } + fn build(&self) -> Self::Entity { + let mut inner = Vec::with_capacity(self.expected_length()); + self.write(&mut inner) + .unwrap_or_else(|_| panic!("{} build should be ok", Self::NAME)); + SighashAll::new_unchecked(inner.into()) + } +} +#[derive(Clone)] +pub struct SighashAllOnly(molecule::bytes::Bytes); +impl ::core::fmt::LowerHex for SighashAllOnly { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + if f.alternate() { + write!(f, "0x")?; + } + write!(f, "{}", hex_string(self.as_slice())) + } +} +impl ::core::fmt::Debug for SighashAllOnly { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}({:#x})", Self::NAME, self) + } +} +impl ::core::fmt::Display for SighashAllOnly { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{} {{ ", Self::NAME)?; + write!(f, "{}: {}", "seal", self.seal())?; + let extra_count = self.count_extra_fields(); + if extra_count != 0 { + write!(f, ", .. ({} fields)", extra_count)?; + } + write!(f, " }}") + } +} +impl ::core::default::Default for SighashAllOnly { + fn default() -> Self { + let v = molecule::bytes::Bytes::from_static(&Self::DEFAULT_VALUE); + SighashAllOnly::new_unchecked(v) + } +} +impl SighashAllOnly { + const DEFAULT_VALUE: [u8; 12] = [12, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0]; + pub const FIELD_COUNT: usize = 1; + pub fn total_size(&self) -> usize { + molecule::unpack_number(self.as_slice()) as usize + } + pub fn field_count(&self) -> usize { + if self.total_size() == molecule::NUMBER_SIZE { + 0 + } else { + (molecule::unpack_number(&self.as_slice()[molecule::NUMBER_SIZE..]) as usize / 4) - 1 + } + } + pub fn count_extra_fields(&self) -> usize { + self.field_count() - Self::FIELD_COUNT + } + pub fn has_extra_fields(&self) -> bool { + Self::FIELD_COUNT != self.field_count() + } + pub fn seal(&self) -> Bytes { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[4..]) as usize; + if self.has_extra_fields() { + let end = molecule::unpack_number(&slice[8..]) as usize; + Bytes::new_unchecked(self.0.slice(start..end)) + } else { + Bytes::new_unchecked(self.0.slice(start..)) + } + } + pub fn as_reader<'r>(&'r self) -> SighashAllOnlyReader<'r> { + SighashAllOnlyReader::new_unchecked(self.as_slice()) + } +} +impl molecule::prelude::Entity for SighashAllOnly { + type Builder = SighashAllOnlyBuilder; + const NAME: &'static str = "SighashAllOnly"; + fn new_unchecked(data: molecule::bytes::Bytes) -> Self { + SighashAllOnly(data) + } + fn as_bytes(&self) -> molecule::bytes::Bytes { + self.0.clone() + } + fn as_slice(&self) -> &[u8] { + &self.0[..] + } + fn from_slice(slice: &[u8]) -> molecule::error::VerificationResult { + SighashAllOnlyReader::from_slice(slice).map(|reader| reader.to_entity()) + } + fn from_compatible_slice(slice: &[u8]) -> molecule::error::VerificationResult { + SighashAllOnlyReader::from_compatible_slice(slice).map(|reader| reader.to_entity()) + } + fn new_builder() -> Self::Builder { + ::core::default::Default::default() + } + fn as_builder(self) -> Self::Builder { + Self::new_builder().seal(self.seal()) + } +} +#[derive(Clone, Copy)] +pub struct SighashAllOnlyReader<'r>(&'r [u8]); +impl<'r> ::core::fmt::LowerHex for SighashAllOnlyReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + if f.alternate() { + write!(f, "0x")?; + } + write!(f, "{}", hex_string(self.as_slice())) + } +} +impl<'r> ::core::fmt::Debug for SighashAllOnlyReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}({:#x})", Self::NAME, self) + } +} +impl<'r> ::core::fmt::Display for SighashAllOnlyReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{} {{ ", Self::NAME)?; + write!(f, "{}: {}", "seal", self.seal())?; + let extra_count = self.count_extra_fields(); + if extra_count != 0 { + write!(f, ", .. ({} fields)", extra_count)?; + } + write!(f, " }}") + } +} +impl<'r> SighashAllOnlyReader<'r> { + pub const FIELD_COUNT: usize = 1; + pub fn total_size(&self) -> usize { + molecule::unpack_number(self.as_slice()) as usize + } + pub fn field_count(&self) -> usize { + if self.total_size() == molecule::NUMBER_SIZE { + 0 + } else { + (molecule::unpack_number(&self.as_slice()[molecule::NUMBER_SIZE..]) as usize / 4) - 1 + } + } + pub fn count_extra_fields(&self) -> usize { + self.field_count() - Self::FIELD_COUNT + } + pub fn has_extra_fields(&self) -> bool { + Self::FIELD_COUNT != self.field_count() + } + pub fn seal(&self) -> BytesReader<'r> { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[4..]) as usize; + if self.has_extra_fields() { + let end = molecule::unpack_number(&slice[8..]) as usize; + BytesReader::new_unchecked(&self.as_slice()[start..end]) + } else { + BytesReader::new_unchecked(&self.as_slice()[start..]) + } + } +} +impl<'r> molecule::prelude::Reader<'r> for SighashAllOnlyReader<'r> { + type Entity = SighashAllOnly; + const NAME: &'static str = "SighashAllOnlyReader"; + fn to_entity(&self) -> Self::Entity { + Self::Entity::new_unchecked(self.as_slice().to_owned().into()) + } + fn new_unchecked(slice: &'r [u8]) -> Self { + SighashAllOnlyReader(slice) + } + fn as_slice(&self) -> &'r [u8] { + self.0 + } + fn verify(slice: &[u8], compatible: bool) -> molecule::error::VerificationResult<()> { + use molecule::verification_error as ve; + let slice_len = slice.len(); + if slice_len < molecule::NUMBER_SIZE { + return ve!(Self, HeaderIsBroken, molecule::NUMBER_SIZE, slice_len); + } + let total_size = molecule::unpack_number(slice) as usize; + if slice_len != total_size { + return ve!(Self, TotalSizeNotMatch, total_size, slice_len); + } + if slice_len < molecule::NUMBER_SIZE * 2 { + return ve!(Self, HeaderIsBroken, molecule::NUMBER_SIZE * 2, slice_len); + } + let offset_first = molecule::unpack_number(&slice[molecule::NUMBER_SIZE..]) as usize; + if offset_first % molecule::NUMBER_SIZE != 0 || offset_first < molecule::NUMBER_SIZE * 2 { + return ve!(Self, OffsetsNotMatch); + } + if slice_len < offset_first { + return ve!(Self, HeaderIsBroken, offset_first, slice_len); + } + let field_count = offset_first / molecule::NUMBER_SIZE - 1; + if field_count < Self::FIELD_COUNT { + return ve!(Self, FieldCountNotMatch, Self::FIELD_COUNT, field_count); + } else if !compatible && field_count > Self::FIELD_COUNT { + return ve!(Self, FieldCountNotMatch, Self::FIELD_COUNT, field_count); + }; + let mut offsets: Vec = slice[molecule::NUMBER_SIZE..offset_first] + .chunks_exact(molecule::NUMBER_SIZE) + .map(|x| molecule::unpack_number(x) as usize) + .collect(); + offsets.push(total_size); + if offsets.windows(2).any(|i| i[0] > i[1]) { + return ve!(Self, OffsetsNotMatch); + } + BytesReader::verify(&slice[offsets[0]..offsets[1]], compatible)?; + Ok(()) + } +} +#[derive(Debug, Default)] +pub struct SighashAllOnlyBuilder { + pub(crate) seal: Bytes, +} +impl SighashAllOnlyBuilder { + pub const FIELD_COUNT: usize = 1; + pub fn seal(mut self, v: Bytes) -> Self { + self.seal = v; + self + } +} +impl molecule::prelude::Builder for SighashAllOnlyBuilder { + type Entity = SighashAllOnly; + const NAME: &'static str = "SighashAllOnlyBuilder"; + fn expected_length(&self) -> usize { + molecule::NUMBER_SIZE * (Self::FIELD_COUNT + 1) + self.seal.as_slice().len() + } + fn write(&self, writer: &mut W) -> molecule::io::Result<()> { + let mut total_size = molecule::NUMBER_SIZE * (Self::FIELD_COUNT + 1); + let mut offsets = Vec::with_capacity(Self::FIELD_COUNT); + offsets.push(total_size); + total_size += self.seal.as_slice().len(); + writer.write_all(&molecule::pack_number(total_size as molecule::Number))?; + for offset in offsets.into_iter() { + writer.write_all(&molecule::pack_number(offset as molecule::Number))?; + } + writer.write_all(self.seal.as_slice())?; + Ok(()) + } + fn build(&self) -> Self::Entity { + let mut inner = Vec::with_capacity(self.expected_length()); + self.write(&mut inner) + .unwrap_or_else(|_| panic!("{} build should be ok", Self::NAME)); + SighashAllOnly::new_unchecked(inner.into()) + } +} +#[derive(Clone)] +pub struct SealPair(molecule::bytes::Bytes); +impl ::core::fmt::LowerHex for SealPair { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + if f.alternate() { + write!(f, "0x")?; + } + write!(f, "{}", hex_string(self.as_slice())) + } +} +impl ::core::fmt::Debug for SealPair { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}({:#x})", Self::NAME, self) + } +} +impl ::core::fmt::Display for SealPair { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{} {{ ", Self::NAME)?; + write!(f, "{}: {}", "script_hash", self.script_hash())?; + write!(f, ", {}: {}", "seal", self.seal())?; + let extra_count = self.count_extra_fields(); + if extra_count != 0 { + write!(f, ", .. ({} fields)", extra_count)?; + } + write!(f, " }}") + } +} +impl ::core::default::Default for SealPair { + fn default() -> Self { + let v = molecule::bytes::Bytes::from_static(&Self::DEFAULT_VALUE); + SealPair::new_unchecked(v) + } +} +impl SealPair { + const DEFAULT_VALUE: [u8; 48] = [ + 48, 0, 0, 0, 12, 0, 0, 0, 44, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ]; + pub const FIELD_COUNT: usize = 2; + pub fn total_size(&self) -> usize { + molecule::unpack_number(self.as_slice()) as usize + } + pub fn field_count(&self) -> usize { + if self.total_size() == molecule::NUMBER_SIZE { + 0 + } else { + (molecule::unpack_number(&self.as_slice()[molecule::NUMBER_SIZE..]) as usize / 4) - 1 + } + } + pub fn count_extra_fields(&self) -> usize { + self.field_count() - Self::FIELD_COUNT + } + pub fn has_extra_fields(&self) -> bool { + Self::FIELD_COUNT != self.field_count() + } + pub fn script_hash(&self) -> Byte32 { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[4..]) as usize; + let end = molecule::unpack_number(&slice[8..]) as usize; + Byte32::new_unchecked(self.0.slice(start..end)) + } + pub fn seal(&self) -> Bytes { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[8..]) as usize; + if self.has_extra_fields() { + let end = molecule::unpack_number(&slice[12..]) as usize; + Bytes::new_unchecked(self.0.slice(start..end)) + } else { + Bytes::new_unchecked(self.0.slice(start..)) + } + } + pub fn as_reader<'r>(&'r self) -> SealPairReader<'r> { + SealPairReader::new_unchecked(self.as_slice()) + } +} +impl molecule::prelude::Entity for SealPair { + type Builder = SealPairBuilder; + const NAME: &'static str = "SealPair"; + fn new_unchecked(data: molecule::bytes::Bytes) -> Self { + SealPair(data) + } + fn as_bytes(&self) -> molecule::bytes::Bytes { + self.0.clone() + } + fn as_slice(&self) -> &[u8] { + &self.0[..] + } + fn from_slice(slice: &[u8]) -> molecule::error::VerificationResult { + SealPairReader::from_slice(slice).map(|reader| reader.to_entity()) + } + fn from_compatible_slice(slice: &[u8]) -> molecule::error::VerificationResult { + SealPairReader::from_compatible_slice(slice).map(|reader| reader.to_entity()) + } + fn new_builder() -> Self::Builder { + ::core::default::Default::default() + } + fn as_builder(self) -> Self::Builder { + Self::new_builder() + .script_hash(self.script_hash()) + .seal(self.seal()) + } +} +#[derive(Clone, Copy)] +pub struct SealPairReader<'r>(&'r [u8]); +impl<'r> ::core::fmt::LowerHex for SealPairReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + if f.alternate() { + write!(f, "0x")?; + } + write!(f, "{}", hex_string(self.as_slice())) + } +} +impl<'r> ::core::fmt::Debug for SealPairReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}({:#x})", Self::NAME, self) + } +} +impl<'r> ::core::fmt::Display for SealPairReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{} {{ ", Self::NAME)?; + write!(f, "{}: {}", "script_hash", self.script_hash())?; + write!(f, ", {}: {}", "seal", self.seal())?; + let extra_count = self.count_extra_fields(); + if extra_count != 0 { + write!(f, ", .. ({} fields)", extra_count)?; + } + write!(f, " }}") + } +} +impl<'r> SealPairReader<'r> { + pub const FIELD_COUNT: usize = 2; + pub fn total_size(&self) -> usize { + molecule::unpack_number(self.as_slice()) as usize + } + pub fn field_count(&self) -> usize { + if self.total_size() == molecule::NUMBER_SIZE { + 0 + } else { + (molecule::unpack_number(&self.as_slice()[molecule::NUMBER_SIZE..]) as usize / 4) - 1 + } + } + pub fn count_extra_fields(&self) -> usize { + self.field_count() - Self::FIELD_COUNT + } + pub fn has_extra_fields(&self) -> bool { + Self::FIELD_COUNT != self.field_count() + } + pub fn script_hash(&self) -> Byte32Reader<'r> { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[4..]) as usize; + let end = molecule::unpack_number(&slice[8..]) as usize; + Byte32Reader::new_unchecked(&self.as_slice()[start..end]) + } + pub fn seal(&self) -> BytesReader<'r> { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[8..]) as usize; + if self.has_extra_fields() { + let end = molecule::unpack_number(&slice[12..]) as usize; + BytesReader::new_unchecked(&self.as_slice()[start..end]) + } else { + BytesReader::new_unchecked(&self.as_slice()[start..]) + } + } +} +impl<'r> molecule::prelude::Reader<'r> for SealPairReader<'r> { + type Entity = SealPair; + const NAME: &'static str = "SealPairReader"; + fn to_entity(&self) -> Self::Entity { + Self::Entity::new_unchecked(self.as_slice().to_owned().into()) + } + fn new_unchecked(slice: &'r [u8]) -> Self { + SealPairReader(slice) + } + fn as_slice(&self) -> &'r [u8] { + self.0 + } + fn verify(slice: &[u8], compatible: bool) -> molecule::error::VerificationResult<()> { + use molecule::verification_error as ve; + let slice_len = slice.len(); + if slice_len < molecule::NUMBER_SIZE { + return ve!(Self, HeaderIsBroken, molecule::NUMBER_SIZE, slice_len); + } + let total_size = molecule::unpack_number(slice) as usize; + if slice_len != total_size { + return ve!(Self, TotalSizeNotMatch, total_size, slice_len); + } + if slice_len < molecule::NUMBER_SIZE * 2 { + return ve!(Self, HeaderIsBroken, molecule::NUMBER_SIZE * 2, slice_len); + } + let offset_first = molecule::unpack_number(&slice[molecule::NUMBER_SIZE..]) as usize; + if offset_first % molecule::NUMBER_SIZE != 0 || offset_first < molecule::NUMBER_SIZE * 2 { + return ve!(Self, OffsetsNotMatch); + } + if slice_len < offset_first { + return ve!(Self, HeaderIsBroken, offset_first, slice_len); + } + let field_count = offset_first / molecule::NUMBER_SIZE - 1; + if field_count < Self::FIELD_COUNT { + return ve!(Self, FieldCountNotMatch, Self::FIELD_COUNT, field_count); + } else if !compatible && field_count > Self::FIELD_COUNT { + return ve!(Self, FieldCountNotMatch, Self::FIELD_COUNT, field_count); + }; + let mut offsets: Vec = slice[molecule::NUMBER_SIZE..offset_first] + .chunks_exact(molecule::NUMBER_SIZE) + .map(|x| molecule::unpack_number(x) as usize) + .collect(); + offsets.push(total_size); + if offsets.windows(2).any(|i| i[0] > i[1]) { + return ve!(Self, OffsetsNotMatch); + } + Byte32Reader::verify(&slice[offsets[0]..offsets[1]], compatible)?; + BytesReader::verify(&slice[offsets[1]..offsets[2]], compatible)?; + Ok(()) + } +} +#[derive(Debug, Default)] +pub struct SealPairBuilder { + pub(crate) script_hash: Byte32, + pub(crate) seal: Bytes, +} +impl SealPairBuilder { + pub const FIELD_COUNT: usize = 2; + pub fn script_hash(mut self, v: Byte32) -> Self { + self.script_hash = v; + self + } + pub fn seal(mut self, v: Bytes) -> Self { + self.seal = v; + self + } +} +impl molecule::prelude::Builder for SealPairBuilder { + type Entity = SealPair; + const NAME: &'static str = "SealPairBuilder"; + fn expected_length(&self) -> usize { + molecule::NUMBER_SIZE * (Self::FIELD_COUNT + 1) + + self.script_hash.as_slice().len() + + self.seal.as_slice().len() + } + fn write(&self, writer: &mut W) -> molecule::io::Result<()> { + let mut total_size = molecule::NUMBER_SIZE * (Self::FIELD_COUNT + 1); + let mut offsets = Vec::with_capacity(Self::FIELD_COUNT); + offsets.push(total_size); + total_size += self.script_hash.as_slice().len(); + offsets.push(total_size); + total_size += self.seal.as_slice().len(); + writer.write_all(&molecule::pack_number(total_size as molecule::Number))?; + for offset in offsets.into_iter() { + writer.write_all(&molecule::pack_number(offset as molecule::Number))?; + } + writer.write_all(self.script_hash.as_slice())?; + writer.write_all(self.seal.as_slice())?; + Ok(()) + } + fn build(&self) -> Self::Entity { + let mut inner = Vec::with_capacity(self.expected_length()); + self.write(&mut inner) + .unwrap_or_else(|_| panic!("{} build should be ok", Self::NAME)); + SealPair::new_unchecked(inner.into()) + } +} +#[derive(Clone)] +pub struct SealPairVec(molecule::bytes::Bytes); +impl ::core::fmt::LowerHex for SealPairVec { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + if f.alternate() { + write!(f, "0x")?; + } + write!(f, "{}", hex_string(self.as_slice())) + } +} +impl ::core::fmt::Debug for SealPairVec { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}({:#x})", Self::NAME, self) + } +} +impl ::core::fmt::Display for SealPairVec { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{} [", Self::NAME)?; + for i in 0..self.len() { + if i == 0 { + write!(f, "{}", self.get_unchecked(i))?; + } else { + write!(f, ", {}", self.get_unchecked(i))?; + } + } + write!(f, "]") + } +} +impl ::core::default::Default for SealPairVec { + fn default() -> Self { + let v = molecule::bytes::Bytes::from_static(&Self::DEFAULT_VALUE); + SealPairVec::new_unchecked(v) + } +} +impl SealPairVec { + const DEFAULT_VALUE: [u8; 4] = [4, 0, 0, 0]; + pub fn total_size(&self) -> usize { + molecule::unpack_number(self.as_slice()) as usize + } + pub fn item_count(&self) -> usize { + if self.total_size() == molecule::NUMBER_SIZE { + 0 + } else { + (molecule::unpack_number(&self.as_slice()[molecule::NUMBER_SIZE..]) as usize / 4) - 1 + } + } + pub fn len(&self) -> usize { + self.item_count() + } + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + pub fn get(&self, idx: usize) -> Option { + if idx >= self.len() { + None + } else { + Some(self.get_unchecked(idx)) + } + } + pub fn get_unchecked(&self, idx: usize) -> SealPair { + let slice = self.as_slice(); + let start_idx = molecule::NUMBER_SIZE * (1 + idx); + let start = molecule::unpack_number(&slice[start_idx..]) as usize; + if idx == self.len() - 1 { + SealPair::new_unchecked(self.0.slice(start..)) + } else { + let end_idx = start_idx + molecule::NUMBER_SIZE; + let end = molecule::unpack_number(&slice[end_idx..]) as usize; + SealPair::new_unchecked(self.0.slice(start..end)) + } + } + pub fn as_reader<'r>(&'r self) -> SealPairVecReader<'r> { + SealPairVecReader::new_unchecked(self.as_slice()) + } +} +impl molecule::prelude::Entity for SealPairVec { + type Builder = SealPairVecBuilder; + const NAME: &'static str = "SealPairVec"; + fn new_unchecked(data: molecule::bytes::Bytes) -> Self { + SealPairVec(data) + } + fn as_bytes(&self) -> molecule::bytes::Bytes { + self.0.clone() + } + fn as_slice(&self) -> &[u8] { + &self.0[..] + } + fn from_slice(slice: &[u8]) -> molecule::error::VerificationResult { + SealPairVecReader::from_slice(slice).map(|reader| reader.to_entity()) + } + fn from_compatible_slice(slice: &[u8]) -> molecule::error::VerificationResult { + SealPairVecReader::from_compatible_slice(slice).map(|reader| reader.to_entity()) + } + fn new_builder() -> Self::Builder { + ::core::default::Default::default() + } + fn as_builder(self) -> Self::Builder { + Self::new_builder().extend(self.into_iter()) + } +} +#[derive(Clone, Copy)] +pub struct SealPairVecReader<'r>(&'r [u8]); +impl<'r> ::core::fmt::LowerHex for SealPairVecReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + if f.alternate() { + write!(f, "0x")?; + } + write!(f, "{}", hex_string(self.as_slice())) + } +} +impl<'r> ::core::fmt::Debug for SealPairVecReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}({:#x})", Self::NAME, self) + } +} +impl<'r> ::core::fmt::Display for SealPairVecReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{} [", Self::NAME)?; + for i in 0..self.len() { + if i == 0 { + write!(f, "{}", self.get_unchecked(i))?; + } else { + write!(f, ", {}", self.get_unchecked(i))?; + } + } + write!(f, "]") + } +} +impl<'r> SealPairVecReader<'r> { + pub fn total_size(&self) -> usize { + molecule::unpack_number(self.as_slice()) as usize + } + pub fn item_count(&self) -> usize { + if self.total_size() == molecule::NUMBER_SIZE { + 0 + } else { + (molecule::unpack_number(&self.as_slice()[molecule::NUMBER_SIZE..]) as usize / 4) - 1 + } + } + pub fn len(&self) -> usize { + self.item_count() + } + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + pub fn get(&self, idx: usize) -> Option> { + if idx >= self.len() { + None + } else { + Some(self.get_unchecked(idx)) + } + } + pub fn get_unchecked(&self, idx: usize) -> SealPairReader<'r> { + let slice = self.as_slice(); + let start_idx = molecule::NUMBER_SIZE * (1 + idx); + let start = molecule::unpack_number(&slice[start_idx..]) as usize; + if idx == self.len() - 1 { + SealPairReader::new_unchecked(&self.as_slice()[start..]) + } else { + let end_idx = start_idx + molecule::NUMBER_SIZE; + let end = molecule::unpack_number(&slice[end_idx..]) as usize; + SealPairReader::new_unchecked(&self.as_slice()[start..end]) + } + } +} +impl<'r> molecule::prelude::Reader<'r> for SealPairVecReader<'r> { + type Entity = SealPairVec; + const NAME: &'static str = "SealPairVecReader"; + fn to_entity(&self) -> Self::Entity { + Self::Entity::new_unchecked(self.as_slice().to_owned().into()) + } + fn new_unchecked(slice: &'r [u8]) -> Self { + SealPairVecReader(slice) + } + fn as_slice(&self) -> &'r [u8] { + self.0 + } + fn verify(slice: &[u8], compatible: bool) -> molecule::error::VerificationResult<()> { + use molecule::verification_error as ve; + let slice_len = slice.len(); + if slice_len < molecule::NUMBER_SIZE { + return ve!(Self, HeaderIsBroken, molecule::NUMBER_SIZE, slice_len); + } + let total_size = molecule::unpack_number(slice) as usize; + if slice_len != total_size { + return ve!(Self, TotalSizeNotMatch, total_size, slice_len); + } + if slice_len == molecule::NUMBER_SIZE { + return Ok(()); + } + if slice_len < molecule::NUMBER_SIZE * 2 { + return ve!( + Self, + TotalSizeNotMatch, + molecule::NUMBER_SIZE * 2, + slice_len + ); + } + let offset_first = molecule::unpack_number(&slice[molecule::NUMBER_SIZE..]) as usize; + if offset_first % molecule::NUMBER_SIZE != 0 || offset_first < molecule::NUMBER_SIZE * 2 { + return ve!(Self, OffsetsNotMatch); + } + if slice_len < offset_first { + return ve!(Self, HeaderIsBroken, offset_first, slice_len); + } + let mut offsets: Vec = slice[molecule::NUMBER_SIZE..offset_first] + .chunks_exact(molecule::NUMBER_SIZE) + .map(|x| molecule::unpack_number(x) as usize) + .collect(); + offsets.push(total_size); + if offsets.windows(2).any(|i| i[0] > i[1]) { + return ve!(Self, OffsetsNotMatch); + } + for pair in offsets.windows(2) { + let start = pair[0]; + let end = pair[1]; + SealPairReader::verify(&slice[start..end], compatible)?; + } + Ok(()) + } +} +#[derive(Debug, Default)] +pub struct SealPairVecBuilder(pub(crate) Vec); +impl SealPairVecBuilder { + pub fn set(mut self, v: Vec) -> Self { + self.0 = v; + self + } + pub fn push(mut self, v: SealPair) -> Self { + self.0.push(v); + self + } + pub fn extend>(mut self, iter: T) -> Self { + for elem in iter { + self.0.push(elem); + } + self + } + pub fn replace(&mut self, index: usize, v: SealPair) -> Option { + self.0 + .get_mut(index) + .map(|item| ::core::mem::replace(item, v)) + } +} +impl molecule::prelude::Builder for SealPairVecBuilder { + type Entity = SealPairVec; + const NAME: &'static str = "SealPairVecBuilder"; + fn expected_length(&self) -> usize { + molecule::NUMBER_SIZE * (self.0.len() + 1) + + self + .0 + .iter() + .map(|inner| inner.as_slice().len()) + .sum::() + } + fn write(&self, writer: &mut W) -> molecule::io::Result<()> { + let item_count = self.0.len(); + if item_count == 0 { + writer.write_all(&molecule::pack_number( + molecule::NUMBER_SIZE as molecule::Number, + ))?; + } else { + let (total_size, offsets) = self.0.iter().fold( + ( + molecule::NUMBER_SIZE * (item_count + 1), + Vec::with_capacity(item_count), + ), + |(start, mut offsets), inner| { + offsets.push(start); + (start + inner.as_slice().len(), offsets) + }, + ); + writer.write_all(&molecule::pack_number(total_size as molecule::Number))?; + for offset in offsets.into_iter() { + writer.write_all(&molecule::pack_number(offset as molecule::Number))?; + } + for inner in self.0.iter() { + writer.write_all(inner.as_slice())?; + } + } + Ok(()) + } + fn build(&self) -> Self::Entity { + let mut inner = Vec::with_capacity(self.expected_length()); + self.write(&mut inner) + .unwrap_or_else(|_| panic!("{} build should be ok", Self::NAME)); + SealPairVec::new_unchecked(inner.into()) + } +} +pub struct SealPairVecIterator(SealPairVec, usize, usize); +impl ::core::iter::Iterator for SealPairVecIterator { + type Item = SealPair; + fn next(&mut self) -> Option { + if self.1 >= self.2 { + None + } else { + let ret = self.0.get_unchecked(self.1); + self.1 += 1; + Some(ret) + } + } +} +impl ::core::iter::ExactSizeIterator for SealPairVecIterator { + fn len(&self) -> usize { + self.2 - self.1 + } +} +impl ::core::iter::IntoIterator for SealPairVec { + type Item = SealPair; + type IntoIter = SealPairVecIterator; + fn into_iter(self) -> Self::IntoIter { + let len = self.len(); + SealPairVecIterator(self, 0, len) + } +} +impl<'r> SealPairVecReader<'r> { + pub fn iter<'t>(&'t self) -> SealPairVecReaderIterator<'t, 'r> { + SealPairVecReaderIterator(&self, 0, self.len()) + } +} +pub struct SealPairVecReaderIterator<'t, 'r>(&'t SealPairVecReader<'r>, usize, usize); +impl<'t: 'r, 'r> ::core::iter::Iterator for SealPairVecReaderIterator<'t, 'r> { + type Item = SealPairReader<'t>; + fn next(&mut self) -> Option { + if self.1 >= self.2 { + None + } else { + let ret = self.0.get_unchecked(self.1); + self.1 += 1; + Some(ret) + } + } +} +impl<'t: 'r, 'r> ::core::iter::ExactSizeIterator for SealPairVecReaderIterator<'t, 'r> { + fn len(&self) -> usize { + self.2 - self.1 + } +} +#[derive(Clone)] +pub struct OtxStart(molecule::bytes::Bytes); +impl ::core::fmt::LowerHex for OtxStart { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + if f.alternate() { + write!(f, "0x")?; + } + write!(f, "{}", hex_string(self.as_slice())) + } +} +impl ::core::fmt::Debug for OtxStart { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}({:#x})", Self::NAME, self) + } +} +impl ::core::fmt::Display for OtxStart { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{} {{ ", Self::NAME)?; + write!(f, "{}: {}", "start_input_cell", self.start_input_cell())?; + write!(f, ", {}: {}", "start_output_cell", self.start_output_cell())?; + write!(f, ", {}: {}", "start_cell_deps", self.start_cell_deps())?; + write!(f, ", {}: {}", "start_header_deps", self.start_header_deps())?; + let extra_count = self.count_extra_fields(); + if extra_count != 0 { + write!(f, ", .. ({} fields)", extra_count)?; + } + write!(f, " }}") + } +} +impl ::core::default::Default for OtxStart { + fn default() -> Self { + let v = molecule::bytes::Bytes::from_static(&Self::DEFAULT_VALUE); + OtxStart::new_unchecked(v) + } +} +impl OtxStart { + const DEFAULT_VALUE: [u8; 36] = [ + 36, 0, 0, 0, 20, 0, 0, 0, 24, 0, 0, 0, 28, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, + ]; + pub const FIELD_COUNT: usize = 4; + pub fn total_size(&self) -> usize { + molecule::unpack_number(self.as_slice()) as usize + } + pub fn field_count(&self) -> usize { + if self.total_size() == molecule::NUMBER_SIZE { + 0 + } else { + (molecule::unpack_number(&self.as_slice()[molecule::NUMBER_SIZE..]) as usize / 4) - 1 + } + } + pub fn count_extra_fields(&self) -> usize { + self.field_count() - Self::FIELD_COUNT + } + pub fn has_extra_fields(&self) -> bool { + Self::FIELD_COUNT != self.field_count() + } + pub fn start_input_cell(&self) -> Uint32 { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[4..]) as usize; + let end = molecule::unpack_number(&slice[8..]) as usize; + Uint32::new_unchecked(self.0.slice(start..end)) + } + pub fn start_output_cell(&self) -> Uint32 { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[8..]) as usize; + let end = molecule::unpack_number(&slice[12..]) as usize; + Uint32::new_unchecked(self.0.slice(start..end)) + } + pub fn start_cell_deps(&self) -> Uint32 { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[12..]) as usize; + let end = molecule::unpack_number(&slice[16..]) as usize; + Uint32::new_unchecked(self.0.slice(start..end)) + } + pub fn start_header_deps(&self) -> Uint32 { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[16..]) as usize; + if self.has_extra_fields() { + let end = molecule::unpack_number(&slice[20..]) as usize; + Uint32::new_unchecked(self.0.slice(start..end)) + } else { + Uint32::new_unchecked(self.0.slice(start..)) + } + } + pub fn as_reader<'r>(&'r self) -> OtxStartReader<'r> { + OtxStartReader::new_unchecked(self.as_slice()) + } +} +impl molecule::prelude::Entity for OtxStart { + type Builder = OtxStartBuilder; + const NAME: &'static str = "OtxStart"; + fn new_unchecked(data: molecule::bytes::Bytes) -> Self { + OtxStart(data) + } + fn as_bytes(&self) -> molecule::bytes::Bytes { + self.0.clone() + } + fn as_slice(&self) -> &[u8] { + &self.0[..] + } + fn from_slice(slice: &[u8]) -> molecule::error::VerificationResult { + OtxStartReader::from_slice(slice).map(|reader| reader.to_entity()) + } + fn from_compatible_slice(slice: &[u8]) -> molecule::error::VerificationResult { + OtxStartReader::from_compatible_slice(slice).map(|reader| reader.to_entity()) + } + fn new_builder() -> Self::Builder { + ::core::default::Default::default() + } + fn as_builder(self) -> Self::Builder { + Self::new_builder() + .start_input_cell(self.start_input_cell()) + .start_output_cell(self.start_output_cell()) + .start_cell_deps(self.start_cell_deps()) + .start_header_deps(self.start_header_deps()) + } +} +#[derive(Clone, Copy)] +pub struct OtxStartReader<'r>(&'r [u8]); +impl<'r> ::core::fmt::LowerHex for OtxStartReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + if f.alternate() { + write!(f, "0x")?; + } + write!(f, "{}", hex_string(self.as_slice())) + } +} +impl<'r> ::core::fmt::Debug for OtxStartReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}({:#x})", Self::NAME, self) + } +} +impl<'r> ::core::fmt::Display for OtxStartReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{} {{ ", Self::NAME)?; + write!(f, "{}: {}", "start_input_cell", self.start_input_cell())?; + write!(f, ", {}: {}", "start_output_cell", self.start_output_cell())?; + write!(f, ", {}: {}", "start_cell_deps", self.start_cell_deps())?; + write!(f, ", {}: {}", "start_header_deps", self.start_header_deps())?; + let extra_count = self.count_extra_fields(); + if extra_count != 0 { + write!(f, ", .. ({} fields)", extra_count)?; + } + write!(f, " }}") + } +} +impl<'r> OtxStartReader<'r> { + pub const FIELD_COUNT: usize = 4; + pub fn total_size(&self) -> usize { + molecule::unpack_number(self.as_slice()) as usize + } + pub fn field_count(&self) -> usize { + if self.total_size() == molecule::NUMBER_SIZE { + 0 + } else { + (molecule::unpack_number(&self.as_slice()[molecule::NUMBER_SIZE..]) as usize / 4) - 1 + } + } + pub fn count_extra_fields(&self) -> usize { + self.field_count() - Self::FIELD_COUNT + } + pub fn has_extra_fields(&self) -> bool { + Self::FIELD_COUNT != self.field_count() + } + pub fn start_input_cell(&self) -> Uint32Reader<'r> { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[4..]) as usize; + let end = molecule::unpack_number(&slice[8..]) as usize; + Uint32Reader::new_unchecked(&self.as_slice()[start..end]) + } + pub fn start_output_cell(&self) -> Uint32Reader<'r> { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[8..]) as usize; + let end = molecule::unpack_number(&slice[12..]) as usize; + Uint32Reader::new_unchecked(&self.as_slice()[start..end]) + } + pub fn start_cell_deps(&self) -> Uint32Reader<'r> { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[12..]) as usize; + let end = molecule::unpack_number(&slice[16..]) as usize; + Uint32Reader::new_unchecked(&self.as_slice()[start..end]) + } + pub fn start_header_deps(&self) -> Uint32Reader<'r> { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[16..]) as usize; + if self.has_extra_fields() { + let end = molecule::unpack_number(&slice[20..]) as usize; + Uint32Reader::new_unchecked(&self.as_slice()[start..end]) + } else { + Uint32Reader::new_unchecked(&self.as_slice()[start..]) + } + } +} +impl<'r> molecule::prelude::Reader<'r> for OtxStartReader<'r> { + type Entity = OtxStart; + const NAME: &'static str = "OtxStartReader"; + fn to_entity(&self) -> Self::Entity { + Self::Entity::new_unchecked(self.as_slice().to_owned().into()) + } + fn new_unchecked(slice: &'r [u8]) -> Self { + OtxStartReader(slice) + } + fn as_slice(&self) -> &'r [u8] { + self.0 + } + fn verify(slice: &[u8], compatible: bool) -> molecule::error::VerificationResult<()> { + use molecule::verification_error as ve; + let slice_len = slice.len(); + if slice_len < molecule::NUMBER_SIZE { + return ve!(Self, HeaderIsBroken, molecule::NUMBER_SIZE, slice_len); + } + let total_size = molecule::unpack_number(slice) as usize; + if slice_len != total_size { + return ve!(Self, TotalSizeNotMatch, total_size, slice_len); + } + if slice_len < molecule::NUMBER_SIZE * 2 { + return ve!(Self, HeaderIsBroken, molecule::NUMBER_SIZE * 2, slice_len); + } + let offset_first = molecule::unpack_number(&slice[molecule::NUMBER_SIZE..]) as usize; + if offset_first % molecule::NUMBER_SIZE != 0 || offset_first < molecule::NUMBER_SIZE * 2 { + return ve!(Self, OffsetsNotMatch); + } + if slice_len < offset_first { + return ve!(Self, HeaderIsBroken, offset_first, slice_len); + } + let field_count = offset_first / molecule::NUMBER_SIZE - 1; + if field_count < Self::FIELD_COUNT { + return ve!(Self, FieldCountNotMatch, Self::FIELD_COUNT, field_count); + } else if !compatible && field_count > Self::FIELD_COUNT { + return ve!(Self, FieldCountNotMatch, Self::FIELD_COUNT, field_count); + }; + let mut offsets: Vec = slice[molecule::NUMBER_SIZE..offset_first] + .chunks_exact(molecule::NUMBER_SIZE) + .map(|x| molecule::unpack_number(x) as usize) + .collect(); + offsets.push(total_size); + if offsets.windows(2).any(|i| i[0] > i[1]) { + return ve!(Self, OffsetsNotMatch); + } + Uint32Reader::verify(&slice[offsets[0]..offsets[1]], compatible)?; + Uint32Reader::verify(&slice[offsets[1]..offsets[2]], compatible)?; + Uint32Reader::verify(&slice[offsets[2]..offsets[3]], compatible)?; + Uint32Reader::verify(&slice[offsets[3]..offsets[4]], compatible)?; + Ok(()) + } +} +#[derive(Debug, Default)] +pub struct OtxStartBuilder { + pub(crate) start_input_cell: Uint32, + pub(crate) start_output_cell: Uint32, + pub(crate) start_cell_deps: Uint32, + pub(crate) start_header_deps: Uint32, +} +impl OtxStartBuilder { + pub const FIELD_COUNT: usize = 4; + pub fn start_input_cell(mut self, v: Uint32) -> Self { + self.start_input_cell = v; + self + } + pub fn start_output_cell(mut self, v: Uint32) -> Self { + self.start_output_cell = v; + self + } + pub fn start_cell_deps(mut self, v: Uint32) -> Self { + self.start_cell_deps = v; + self + } + pub fn start_header_deps(mut self, v: Uint32) -> Self { + self.start_header_deps = v; + self + } +} +impl molecule::prelude::Builder for OtxStartBuilder { + type Entity = OtxStart; + const NAME: &'static str = "OtxStartBuilder"; + fn expected_length(&self) -> usize { + molecule::NUMBER_SIZE * (Self::FIELD_COUNT + 1) + + self.start_input_cell.as_slice().len() + + self.start_output_cell.as_slice().len() + + self.start_cell_deps.as_slice().len() + + self.start_header_deps.as_slice().len() + } + fn write(&self, writer: &mut W) -> molecule::io::Result<()> { + let mut total_size = molecule::NUMBER_SIZE * (Self::FIELD_COUNT + 1); + let mut offsets = Vec::with_capacity(Self::FIELD_COUNT); + offsets.push(total_size); + total_size += self.start_input_cell.as_slice().len(); + offsets.push(total_size); + total_size += self.start_output_cell.as_slice().len(); + offsets.push(total_size); + total_size += self.start_cell_deps.as_slice().len(); + offsets.push(total_size); + total_size += self.start_header_deps.as_slice().len(); + writer.write_all(&molecule::pack_number(total_size as molecule::Number))?; + for offset in offsets.into_iter() { + writer.write_all(&molecule::pack_number(offset as molecule::Number))?; + } + writer.write_all(self.start_input_cell.as_slice())?; + writer.write_all(self.start_output_cell.as_slice())?; + writer.write_all(self.start_cell_deps.as_slice())?; + writer.write_all(self.start_header_deps.as_slice())?; + Ok(()) + } + fn build(&self) -> Self::Entity { + let mut inner = Vec::with_capacity(self.expected_length()); + self.write(&mut inner) + .unwrap_or_else(|_| panic!("{} build should be ok", Self::NAME)); + OtxStart::new_unchecked(inner.into()) + } +} +#[derive(Clone)] +pub struct Otx(molecule::bytes::Bytes); +impl ::core::fmt::LowerHex for Otx { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + if f.alternate() { + write!(f, "0x")?; + } + write!(f, "{}", hex_string(self.as_slice())) + } +} +impl ::core::fmt::Debug for Otx { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}({:#x})", Self::NAME, self) + } +} +impl ::core::fmt::Display for Otx { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{} {{ ", Self::NAME)?; + write!(f, "{}: {}", "input_cells", self.input_cells())?; + write!(f, ", {}: {}", "output_cells", self.output_cells())?; + write!(f, ", {}: {}", "cell_deps", self.cell_deps())?; + write!(f, ", {}: {}", "header_deps", self.header_deps())?; + write!(f, ", {}: {}", "message", self.message())?; + write!(f, ", {}: {}", "seals", self.seals())?; + let extra_count = self.count_extra_fields(); + if extra_count != 0 { + write!(f, ", .. ({} fields)", extra_count)?; + } + write!(f, " }}") + } +} +impl ::core::default::Default for Otx { + fn default() -> Self { + let v = molecule::bytes::Bytes::from_static(&Self::DEFAULT_VALUE); + Otx::new_unchecked(v) + } +} +impl Otx { + const DEFAULT_VALUE: [u8; 60] = [ + 60, 0, 0, 0, 28, 0, 0, 0, 32, 0, 0, 0, 36, 0, 0, 0, 40, 0, 0, 0, 44, 0, 0, 0, 56, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 8, 0, 0, 0, 4, 0, 0, 0, 4, 0, + 0, 0, + ]; + pub const FIELD_COUNT: usize = 6; + pub fn total_size(&self) -> usize { + molecule::unpack_number(self.as_slice()) as usize + } + pub fn field_count(&self) -> usize { + if self.total_size() == molecule::NUMBER_SIZE { + 0 + } else { + (molecule::unpack_number(&self.as_slice()[molecule::NUMBER_SIZE..]) as usize / 4) - 1 + } + } + pub fn count_extra_fields(&self) -> usize { + self.field_count() - Self::FIELD_COUNT + } + pub fn has_extra_fields(&self) -> bool { + Self::FIELD_COUNT != self.field_count() + } + pub fn input_cells(&self) -> Uint32 { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[4..]) as usize; + let end = molecule::unpack_number(&slice[8..]) as usize; + Uint32::new_unchecked(self.0.slice(start..end)) + } + pub fn output_cells(&self) -> Uint32 { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[8..]) as usize; + let end = molecule::unpack_number(&slice[12..]) as usize; + Uint32::new_unchecked(self.0.slice(start..end)) + } + pub fn cell_deps(&self) -> Uint32 { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[12..]) as usize; + let end = molecule::unpack_number(&slice[16..]) as usize; + Uint32::new_unchecked(self.0.slice(start..end)) + } + pub fn header_deps(&self) -> Uint32 { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[16..]) as usize; + let end = molecule::unpack_number(&slice[20..]) as usize; + Uint32::new_unchecked(self.0.slice(start..end)) + } + pub fn message(&self) -> Message { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[20..]) as usize; + let end = molecule::unpack_number(&slice[24..]) as usize; + Message::new_unchecked(self.0.slice(start..end)) + } + pub fn seals(&self) -> SealPairVec { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[24..]) as usize; + if self.has_extra_fields() { + let end = molecule::unpack_number(&slice[28..]) as usize; + SealPairVec::new_unchecked(self.0.slice(start..end)) + } else { + SealPairVec::new_unchecked(self.0.slice(start..)) + } + } + pub fn as_reader<'r>(&'r self) -> OtxReader<'r> { + OtxReader::new_unchecked(self.as_slice()) + } +} +impl molecule::prelude::Entity for Otx { + type Builder = OtxBuilder; + const NAME: &'static str = "Otx"; + fn new_unchecked(data: molecule::bytes::Bytes) -> Self { + Otx(data) + } + fn as_bytes(&self) -> molecule::bytes::Bytes { + self.0.clone() + } + fn as_slice(&self) -> &[u8] { + &self.0[..] + } + fn from_slice(slice: &[u8]) -> molecule::error::VerificationResult { + OtxReader::from_slice(slice).map(|reader| reader.to_entity()) + } + fn from_compatible_slice(slice: &[u8]) -> molecule::error::VerificationResult { + OtxReader::from_compatible_slice(slice).map(|reader| reader.to_entity()) + } + fn new_builder() -> Self::Builder { + ::core::default::Default::default() + } + fn as_builder(self) -> Self::Builder { + Self::new_builder() + .input_cells(self.input_cells()) + .output_cells(self.output_cells()) + .cell_deps(self.cell_deps()) + .header_deps(self.header_deps()) + .message(self.message()) + .seals(self.seals()) + } +} +#[derive(Clone, Copy)] +pub struct OtxReader<'r>(&'r [u8]); +impl<'r> ::core::fmt::LowerHex for OtxReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + if f.alternate() { + write!(f, "0x")?; + } + write!(f, "{}", hex_string(self.as_slice())) + } +} +impl<'r> ::core::fmt::Debug for OtxReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}({:#x})", Self::NAME, self) + } +} +impl<'r> ::core::fmt::Display for OtxReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{} {{ ", Self::NAME)?; + write!(f, "{}: {}", "input_cells", self.input_cells())?; + write!(f, ", {}: {}", "output_cells", self.output_cells())?; + write!(f, ", {}: {}", "cell_deps", self.cell_deps())?; + write!(f, ", {}: {}", "header_deps", self.header_deps())?; + write!(f, ", {}: {}", "message", self.message())?; + write!(f, ", {}: {}", "seals", self.seals())?; + let extra_count = self.count_extra_fields(); + if extra_count != 0 { + write!(f, ", .. ({} fields)", extra_count)?; + } + write!(f, " }}") + } +} +impl<'r> OtxReader<'r> { + pub const FIELD_COUNT: usize = 6; + pub fn total_size(&self) -> usize { + molecule::unpack_number(self.as_slice()) as usize + } + pub fn field_count(&self) -> usize { + if self.total_size() == molecule::NUMBER_SIZE { + 0 + } else { + (molecule::unpack_number(&self.as_slice()[molecule::NUMBER_SIZE..]) as usize / 4) - 1 + } + } + pub fn count_extra_fields(&self) -> usize { + self.field_count() - Self::FIELD_COUNT + } + pub fn has_extra_fields(&self) -> bool { + Self::FIELD_COUNT != self.field_count() + } + pub fn input_cells(&self) -> Uint32Reader<'r> { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[4..]) as usize; + let end = molecule::unpack_number(&slice[8..]) as usize; + Uint32Reader::new_unchecked(&self.as_slice()[start..end]) + } + pub fn output_cells(&self) -> Uint32Reader<'r> { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[8..]) as usize; + let end = molecule::unpack_number(&slice[12..]) as usize; + Uint32Reader::new_unchecked(&self.as_slice()[start..end]) + } + pub fn cell_deps(&self) -> Uint32Reader<'r> { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[12..]) as usize; + let end = molecule::unpack_number(&slice[16..]) as usize; + Uint32Reader::new_unchecked(&self.as_slice()[start..end]) + } + pub fn header_deps(&self) -> Uint32Reader<'r> { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[16..]) as usize; + let end = molecule::unpack_number(&slice[20..]) as usize; + Uint32Reader::new_unchecked(&self.as_slice()[start..end]) + } + pub fn message(&self) -> MessageReader<'r> { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[20..]) as usize; + let end = molecule::unpack_number(&slice[24..]) as usize; + MessageReader::new_unchecked(&self.as_slice()[start..end]) + } + pub fn seals(&self) -> SealPairVecReader<'r> { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[24..]) as usize; + if self.has_extra_fields() { + let end = molecule::unpack_number(&slice[28..]) as usize; + SealPairVecReader::new_unchecked(&self.as_slice()[start..end]) + } else { + SealPairVecReader::new_unchecked(&self.as_slice()[start..]) + } + } +} +impl<'r> molecule::prelude::Reader<'r> for OtxReader<'r> { + type Entity = Otx; + const NAME: &'static str = "OtxReader"; + fn to_entity(&self) -> Self::Entity { + Self::Entity::new_unchecked(self.as_slice().to_owned().into()) + } + fn new_unchecked(slice: &'r [u8]) -> Self { + OtxReader(slice) + } + fn as_slice(&self) -> &'r [u8] { + self.0 + } + fn verify(slice: &[u8], compatible: bool) -> molecule::error::VerificationResult<()> { + use molecule::verification_error as ve; + let slice_len = slice.len(); + if slice_len < molecule::NUMBER_SIZE { + return ve!(Self, HeaderIsBroken, molecule::NUMBER_SIZE, slice_len); + } + let total_size = molecule::unpack_number(slice) as usize; + if slice_len != total_size { + return ve!(Self, TotalSizeNotMatch, total_size, slice_len); + } + if slice_len < molecule::NUMBER_SIZE * 2 { + return ve!(Self, HeaderIsBroken, molecule::NUMBER_SIZE * 2, slice_len); + } + let offset_first = molecule::unpack_number(&slice[molecule::NUMBER_SIZE..]) as usize; + if offset_first % molecule::NUMBER_SIZE != 0 || offset_first < molecule::NUMBER_SIZE * 2 { + return ve!(Self, OffsetsNotMatch); + } + if slice_len < offset_first { + return ve!(Self, HeaderIsBroken, offset_first, slice_len); + } + let field_count = offset_first / molecule::NUMBER_SIZE - 1; + if field_count < Self::FIELD_COUNT { + return ve!(Self, FieldCountNotMatch, Self::FIELD_COUNT, field_count); + } else if !compatible && field_count > Self::FIELD_COUNT { + return ve!(Self, FieldCountNotMatch, Self::FIELD_COUNT, field_count); + }; + let mut offsets: Vec = slice[molecule::NUMBER_SIZE..offset_first] + .chunks_exact(molecule::NUMBER_SIZE) + .map(|x| molecule::unpack_number(x) as usize) + .collect(); + offsets.push(total_size); + if offsets.windows(2).any(|i| i[0] > i[1]) { + return ve!(Self, OffsetsNotMatch); + } + Uint32Reader::verify(&slice[offsets[0]..offsets[1]], compatible)?; + Uint32Reader::verify(&slice[offsets[1]..offsets[2]], compatible)?; + Uint32Reader::verify(&slice[offsets[2]..offsets[3]], compatible)?; + Uint32Reader::verify(&slice[offsets[3]..offsets[4]], compatible)?; + MessageReader::verify(&slice[offsets[4]..offsets[5]], compatible)?; + SealPairVecReader::verify(&slice[offsets[5]..offsets[6]], compatible)?; + Ok(()) + } +} +#[derive(Debug, Default)] +pub struct OtxBuilder { + pub(crate) input_cells: Uint32, + pub(crate) output_cells: Uint32, + pub(crate) cell_deps: Uint32, + pub(crate) header_deps: Uint32, + pub(crate) message: Message, + pub(crate) seals: SealPairVec, +} +impl OtxBuilder { + pub const FIELD_COUNT: usize = 6; + pub fn input_cells(mut self, v: Uint32) -> Self { + self.input_cells = v; + self + } + pub fn output_cells(mut self, v: Uint32) -> Self { + self.output_cells = v; + self + } + pub fn cell_deps(mut self, v: Uint32) -> Self { + self.cell_deps = v; + self + } + pub fn header_deps(mut self, v: Uint32) -> Self { + self.header_deps = v; + self + } + pub fn message(mut self, v: Message) -> Self { + self.message = v; + self + } + pub fn seals(mut self, v: SealPairVec) -> Self { + self.seals = v; + self + } +} +impl molecule::prelude::Builder for OtxBuilder { + type Entity = Otx; + const NAME: &'static str = "OtxBuilder"; + fn expected_length(&self) -> usize { + molecule::NUMBER_SIZE * (Self::FIELD_COUNT + 1) + + self.input_cells.as_slice().len() + + self.output_cells.as_slice().len() + + self.cell_deps.as_slice().len() + + self.header_deps.as_slice().len() + + self.message.as_slice().len() + + self.seals.as_slice().len() + } + fn write(&self, writer: &mut W) -> molecule::io::Result<()> { + let mut total_size = molecule::NUMBER_SIZE * (Self::FIELD_COUNT + 1); + let mut offsets = Vec::with_capacity(Self::FIELD_COUNT); + offsets.push(total_size); + total_size += self.input_cells.as_slice().len(); + offsets.push(total_size); + total_size += self.output_cells.as_slice().len(); + offsets.push(total_size); + total_size += self.cell_deps.as_slice().len(); + offsets.push(total_size); + total_size += self.header_deps.as_slice().len(); + offsets.push(total_size); + total_size += self.message.as_slice().len(); + offsets.push(total_size); + total_size += self.seals.as_slice().len(); + writer.write_all(&molecule::pack_number(total_size as molecule::Number))?; + for offset in offsets.into_iter() { + writer.write_all(&molecule::pack_number(offset as molecule::Number))?; + } + writer.write_all(self.input_cells.as_slice())?; + writer.write_all(self.output_cells.as_slice())?; + writer.write_all(self.cell_deps.as_slice())?; + writer.write_all(self.header_deps.as_slice())?; + writer.write_all(self.message.as_slice())?; + writer.write_all(self.seals.as_slice())?; + Ok(()) + } + fn build(&self) -> Self::Entity { + let mut inner = Vec::with_capacity(self.expected_length()); + self.write(&mut inner) + .unwrap_or_else(|_| panic!("{} build should be ok", Self::NAME)); + Otx::new_unchecked(inner.into()) + } +} diff --git a/src/types/cobuild/mod.rs b/src/types/cobuild/mod.rs new file mode 100644 index 00000000..4fc5711a --- /dev/null +++ b/src/types/cobuild/mod.rs @@ -0,0 +1,3 @@ +pub use ckb_types::packed as blockchain; +pub mod basic; +pub mod top_level; diff --git a/src/types/cobuild/top_level.rs b/src/types/cobuild/top_level.rs new file mode 100644 index 00000000..39ebda99 --- /dev/null +++ b/src/types/cobuild/top_level.rs @@ -0,0 +1,367 @@ +// Generated by Molecule 0.7.5 + +use ckb_types::molecule; +// these lines above are manually added + +use super::basic::*; +use molecule::prelude::*; +#[derive(Clone)] +pub struct WitnessLayout(molecule::bytes::Bytes); +impl ::core::fmt::LowerHex for WitnessLayout { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + if f.alternate() { + write!(f, "0x")?; + } + write!(f, "{}", hex_string(self.as_slice())) + } +} +impl ::core::fmt::Debug for WitnessLayout { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}({:#x})", Self::NAME, self) + } +} +impl ::core::fmt::Display for WitnessLayout { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}(", Self::NAME)?; + self.to_enum().display_inner(f)?; + write!(f, ")") + } +} +impl ::core::default::Default for WitnessLayout { + fn default() -> Self { + let v = molecule::bytes::Bytes::from_static(&Self::DEFAULT_VALUE); + WitnessLayout::new_unchecked(v) + } +} +impl WitnessLayout { + const DEFAULT_VALUE: [u8; 32] = [ + 1, 0, 0, 255, 28, 0, 0, 0, 12, 0, 0, 0, 24, 0, 0, 0, 12, 0, 0, 0, 8, 0, 0, 0, 4, 0, 0, 0, + 0, 0, 0, 0, + ]; + pub const ITEMS_COUNT: usize = 4; + pub fn item_id(&self) -> molecule::Number { + molecule::unpack_number(self.as_slice()) + } + pub fn to_enum(&self) -> WitnessLayoutUnion { + let inner = self.0.slice(molecule::NUMBER_SIZE..); + match self.item_id() { + 4278190081 => SighashAll::new_unchecked(inner).into(), + 4278190082 => SighashAllOnly::new_unchecked(inner).into(), + 4278190083 => Otx::new_unchecked(inner).into(), + 4278190084 => OtxStart::new_unchecked(inner).into(), + _ => panic!("{}: invalid data", Self::NAME), + } + } + pub fn as_reader<'r>(&'r self) -> WitnessLayoutReader<'r> { + WitnessLayoutReader::new_unchecked(self.as_slice()) + } +} +impl molecule::prelude::Entity for WitnessLayout { + type Builder = WitnessLayoutBuilder; + const NAME: &'static str = "WitnessLayout"; + fn new_unchecked(data: molecule::bytes::Bytes) -> Self { + WitnessLayout(data) + } + fn as_bytes(&self) -> molecule::bytes::Bytes { + self.0.clone() + } + fn as_slice(&self) -> &[u8] { + &self.0[..] + } + fn from_slice(slice: &[u8]) -> molecule::error::VerificationResult { + WitnessLayoutReader::from_slice(slice).map(|reader| reader.to_entity()) + } + fn from_compatible_slice(slice: &[u8]) -> molecule::error::VerificationResult { + WitnessLayoutReader::from_compatible_slice(slice).map(|reader| reader.to_entity()) + } + fn new_builder() -> Self::Builder { + ::core::default::Default::default() + } + fn as_builder(self) -> Self::Builder { + Self::new_builder().set(self.to_enum()) + } +} +#[derive(Clone, Copy)] +pub struct WitnessLayoutReader<'r>(&'r [u8]); +impl<'r> ::core::fmt::LowerHex for WitnessLayoutReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + if f.alternate() { + write!(f, "0x")?; + } + write!(f, "{}", hex_string(self.as_slice())) + } +} +impl<'r> ::core::fmt::Debug for WitnessLayoutReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}({:#x})", Self::NAME, self) + } +} +impl<'r> ::core::fmt::Display for WitnessLayoutReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}(", Self::NAME)?; + self.to_enum().display_inner(f)?; + write!(f, ")") + } +} +impl<'r> WitnessLayoutReader<'r> { + pub const ITEMS_COUNT: usize = 4; + pub fn item_id(&self) -> molecule::Number { + molecule::unpack_number(self.as_slice()) + } + pub fn to_enum(&self) -> WitnessLayoutUnionReader<'r> { + let inner = &self.as_slice()[molecule::NUMBER_SIZE..]; + match self.item_id() { + 4278190081 => SighashAllReader::new_unchecked(inner).into(), + 4278190082 => SighashAllOnlyReader::new_unchecked(inner).into(), + 4278190083 => OtxReader::new_unchecked(inner).into(), + 4278190084 => OtxStartReader::new_unchecked(inner).into(), + _ => panic!("{}: invalid data", Self::NAME), + } + } +} +impl<'r> molecule::prelude::Reader<'r> for WitnessLayoutReader<'r> { + type Entity = WitnessLayout; + const NAME: &'static str = "WitnessLayoutReader"; + fn to_entity(&self) -> Self::Entity { + Self::Entity::new_unchecked(self.as_slice().to_owned().into()) + } + fn new_unchecked(slice: &'r [u8]) -> Self { + WitnessLayoutReader(slice) + } + fn as_slice(&self) -> &'r [u8] { + self.0 + } + fn verify(slice: &[u8], compatible: bool) -> molecule::error::VerificationResult<()> { + use molecule::verification_error as ve; + let slice_len = slice.len(); + if slice_len < molecule::NUMBER_SIZE { + return ve!(Self, HeaderIsBroken, molecule::NUMBER_SIZE, slice_len); + } + let item_id = molecule::unpack_number(slice); + let inner_slice = &slice[molecule::NUMBER_SIZE..]; + match item_id { + 4278190081 => SighashAllReader::verify(inner_slice, compatible), + 4278190082 => SighashAllOnlyReader::verify(inner_slice, compatible), + 4278190083 => OtxReader::verify(inner_slice, compatible), + 4278190084 => OtxStartReader::verify(inner_slice, compatible), + _ => ve!(Self, UnknownItem, Self::ITEMS_COUNT, item_id), + }?; + Ok(()) + } +} +#[derive(Debug, Default)] +pub struct WitnessLayoutBuilder(pub(crate) WitnessLayoutUnion); +impl WitnessLayoutBuilder { + pub const ITEMS_COUNT: usize = 4; + pub fn set(mut self, v: I) -> Self + where + I: ::core::convert::Into, + { + self.0 = v.into(); + self + } +} +impl molecule::prelude::Builder for WitnessLayoutBuilder { + type Entity = WitnessLayout; + const NAME: &'static str = "WitnessLayoutBuilder"; + fn expected_length(&self) -> usize { + molecule::NUMBER_SIZE + self.0.as_slice().len() + } + fn write(&self, writer: &mut W) -> molecule::io::Result<()> { + writer.write_all(&molecule::pack_number(self.0.item_id()))?; + writer.write_all(self.0.as_slice()) + } + fn build(&self) -> Self::Entity { + let mut inner = Vec::with_capacity(self.expected_length()); + self.write(&mut inner) + .unwrap_or_else(|_| panic!("{} build should be ok", Self::NAME)); + WitnessLayout::new_unchecked(inner.into()) + } +} +#[derive(Debug, Clone)] +pub enum WitnessLayoutUnion { + SighashAll(SighashAll), + SighashAllOnly(SighashAllOnly), + Otx(Otx), + OtxStart(OtxStart), +} +#[derive(Debug, Clone, Copy)] +pub enum WitnessLayoutUnionReader<'r> { + SighashAll(SighashAllReader<'r>), + SighashAllOnly(SighashAllOnlyReader<'r>), + Otx(OtxReader<'r>), + OtxStart(OtxStartReader<'r>), +} +impl ::core::default::Default for WitnessLayoutUnion { + fn default() -> Self { + WitnessLayoutUnion::SighashAll(::core::default::Default::default()) + } +} +impl ::core::fmt::Display for WitnessLayoutUnion { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + match self { + WitnessLayoutUnion::SighashAll(ref item) => { + write!(f, "{}::{}({})", Self::NAME, SighashAll::NAME, item) + } + WitnessLayoutUnion::SighashAllOnly(ref item) => { + write!(f, "{}::{}({})", Self::NAME, SighashAllOnly::NAME, item) + } + WitnessLayoutUnion::Otx(ref item) => { + write!(f, "{}::{}({})", Self::NAME, Otx::NAME, item) + } + WitnessLayoutUnion::OtxStart(ref item) => { + write!(f, "{}::{}({})", Self::NAME, OtxStart::NAME, item) + } + } + } +} +impl<'r> ::core::fmt::Display for WitnessLayoutUnionReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + match self { + WitnessLayoutUnionReader::SighashAll(ref item) => { + write!(f, "{}::{}({})", Self::NAME, SighashAll::NAME, item) + } + WitnessLayoutUnionReader::SighashAllOnly(ref item) => { + write!(f, "{}::{}({})", Self::NAME, SighashAllOnly::NAME, item) + } + WitnessLayoutUnionReader::Otx(ref item) => { + write!(f, "{}::{}({})", Self::NAME, Otx::NAME, item) + } + WitnessLayoutUnionReader::OtxStart(ref item) => { + write!(f, "{}::{}({})", Self::NAME, OtxStart::NAME, item) + } + } + } +} +impl WitnessLayoutUnion { + pub(crate) fn display_inner(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + match self { + WitnessLayoutUnion::SighashAll(ref item) => write!(f, "{}", item), + WitnessLayoutUnion::SighashAllOnly(ref item) => write!(f, "{}", item), + WitnessLayoutUnion::Otx(ref item) => write!(f, "{}", item), + WitnessLayoutUnion::OtxStart(ref item) => write!(f, "{}", item), + } + } +} +impl<'r> WitnessLayoutUnionReader<'r> { + pub(crate) fn display_inner(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + match self { + WitnessLayoutUnionReader::SighashAll(ref item) => write!(f, "{}", item), + WitnessLayoutUnionReader::SighashAllOnly(ref item) => write!(f, "{}", item), + WitnessLayoutUnionReader::Otx(ref item) => write!(f, "{}", item), + WitnessLayoutUnionReader::OtxStart(ref item) => write!(f, "{}", item), + } + } +} +impl ::core::convert::From for WitnessLayoutUnion { + fn from(item: SighashAll) -> Self { + WitnessLayoutUnion::SighashAll(item) + } +} +impl ::core::convert::From for WitnessLayoutUnion { + fn from(item: SighashAllOnly) -> Self { + WitnessLayoutUnion::SighashAllOnly(item) + } +} +impl ::core::convert::From for WitnessLayoutUnion { + fn from(item: Otx) -> Self { + WitnessLayoutUnion::Otx(item) + } +} +impl ::core::convert::From for WitnessLayoutUnion { + fn from(item: OtxStart) -> Self { + WitnessLayoutUnion::OtxStart(item) + } +} +impl<'r> ::core::convert::From> for WitnessLayoutUnionReader<'r> { + fn from(item: SighashAllReader<'r>) -> Self { + WitnessLayoutUnionReader::SighashAll(item) + } +} +impl<'r> ::core::convert::From> for WitnessLayoutUnionReader<'r> { + fn from(item: SighashAllOnlyReader<'r>) -> Self { + WitnessLayoutUnionReader::SighashAllOnly(item) + } +} +impl<'r> ::core::convert::From> for WitnessLayoutUnionReader<'r> { + fn from(item: OtxReader<'r>) -> Self { + WitnessLayoutUnionReader::Otx(item) + } +} +impl<'r> ::core::convert::From> for WitnessLayoutUnionReader<'r> { + fn from(item: OtxStartReader<'r>) -> Self { + WitnessLayoutUnionReader::OtxStart(item) + } +} +impl WitnessLayoutUnion { + pub const NAME: &'static str = "WitnessLayoutUnion"; + pub fn as_bytes(&self) -> molecule::bytes::Bytes { + match self { + WitnessLayoutUnion::SighashAll(item) => item.as_bytes(), + WitnessLayoutUnion::SighashAllOnly(item) => item.as_bytes(), + WitnessLayoutUnion::Otx(item) => item.as_bytes(), + WitnessLayoutUnion::OtxStart(item) => item.as_bytes(), + } + } + pub fn as_slice(&self) -> &[u8] { + match self { + WitnessLayoutUnion::SighashAll(item) => item.as_slice(), + WitnessLayoutUnion::SighashAllOnly(item) => item.as_slice(), + WitnessLayoutUnion::Otx(item) => item.as_slice(), + WitnessLayoutUnion::OtxStart(item) => item.as_slice(), + } + } + pub fn item_id(&self) -> molecule::Number { + match self { + WitnessLayoutUnion::SighashAll(_) => 4278190081, + WitnessLayoutUnion::SighashAllOnly(_) => 4278190082, + WitnessLayoutUnion::Otx(_) => 4278190083, + WitnessLayoutUnion::OtxStart(_) => 4278190084, + } + } + pub fn item_name(&self) -> &str { + match self { + WitnessLayoutUnion::SighashAll(_) => "SighashAll", + WitnessLayoutUnion::SighashAllOnly(_) => "SighashAllOnly", + WitnessLayoutUnion::Otx(_) => "Otx", + WitnessLayoutUnion::OtxStart(_) => "OtxStart", + } + } + pub fn as_reader<'r>(&'r self) -> WitnessLayoutUnionReader<'r> { + match self { + WitnessLayoutUnion::SighashAll(item) => item.as_reader().into(), + WitnessLayoutUnion::SighashAllOnly(item) => item.as_reader().into(), + WitnessLayoutUnion::Otx(item) => item.as_reader().into(), + WitnessLayoutUnion::OtxStart(item) => item.as_reader().into(), + } + } +} +impl<'r> WitnessLayoutUnionReader<'r> { + pub const NAME: &'r str = "WitnessLayoutUnionReader"; + pub fn as_slice(&self) -> &'r [u8] { + match self { + WitnessLayoutUnionReader::SighashAll(item) => item.as_slice(), + WitnessLayoutUnionReader::SighashAllOnly(item) => item.as_slice(), + WitnessLayoutUnionReader::Otx(item) => item.as_slice(), + WitnessLayoutUnionReader::OtxStart(item) => item.as_slice(), + } + } + pub fn item_id(&self) -> molecule::Number { + match self { + WitnessLayoutUnionReader::SighashAll(_) => 4278190081, + WitnessLayoutUnionReader::SighashAllOnly(_) => 4278190082, + WitnessLayoutUnionReader::Otx(_) => 4278190083, + WitnessLayoutUnionReader::OtxStart(_) => 4278190084, + } + } + pub fn item_name(&self) -> &str { + match self { + WitnessLayoutUnionReader::SighashAll(_) => "SighashAll", + WitnessLayoutUnionReader::SighashAllOnly(_) => "SighashAllOnly", + WitnessLayoutUnionReader::Otx(_) => "Otx", + WitnessLayoutUnionReader::OtxStart(_) => "OtxStart", + } + } +} diff --git a/src/types/mod.rs b/src/types/mod.rs index 8ca3b033..a2c231ba 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -1,5 +1,7 @@ //! Basic ckb sdk types mod address; +#[allow(clippy::all)] +pub mod cobuild; mod human_capacity; mod network_type; #[allow(clippy::all)] diff --git a/src/types/schemas/cobuild/basic.mol b/src/types/schemas/cobuild/basic.mol new file mode 100644 index 00000000..21a22421 --- /dev/null +++ b/src/types/schemas/cobuild/basic.mol @@ -0,0 +1,81 @@ +import blockchain; + +array Hash [byte; 32]; +vector String ; // UTF-8 encoded +option Uint32Opt (Uint32); + +table Action { + script_info_hash: Byte32, // script info + script_hash: Byte32, // script + data: Bytes, // action data +} + +vector ActionVec ; + +table Message { + actions: ActionVec, +} + +table ScriptInfo { + // The dapp name and domain the script belongs to + name: String, + url: String, + + // Script info. + // schema: script action schema + // message_type: the entry action type used in WitnessLayout + script_hash: Byte32, + schema: String, + message_type: String, +} + +vector ScriptInfoVec ; + +table ResolvedInputs { + outputs: CellOutputVec, + outputs_data: BytesVec, +} + +table BuildingPacketV1 { + message: Message, + payload: Transaction, + resolved_inputs: ResolvedInputs, + change_output: Uint32Opt, + script_infos: ScriptInfoVec, + lock_actions: ActionVec, +} + +union BuildingPacket { + BuildingPacketV1, +} + +table SighashAll { + message: Message, + seal: Bytes, +} + +table SighashAllOnly { + seal: Bytes, +} + +table SealPair { + script_hash: Byte32, + seal: Bytes, +} +vector SealPairVec ; + +table OtxStart { + start_input_cell: Uint32, + start_output_cell: Uint32, + start_cell_deps: Uint32, + start_header_deps: Uint32, +} + +table Otx { + input_cells: Uint32, + output_cells: Uint32, + cell_deps: Uint32, + header_deps: Uint32, + message: Message, + seals: SealPairVec, +} \ No newline at end of file diff --git a/src/types/schemas/cobuild/top_level.mol b/src/types/schemas/cobuild/top_level.mol new file mode 100644 index 00000000..7ab2ad39 --- /dev/null +++ b/src/types/schemas/cobuild/top_level.mol @@ -0,0 +1,8 @@ +import basic; + +union WitnessLayout { + SighashAll: 4278190081, + SighashAllOnly: 4278190082, + Otx: 4278190083, + OtxStart: 4278190084, +} diff --git a/src/types/script_id.rs b/src/types/script_id.rs index 9e0476af..fd30ca26 100644 --- a/src/types/script_id.rs +++ b/src/types/script_id.rs @@ -36,6 +36,7 @@ impl ScriptId { } /// Generate a dummy TypeId script with a placeholder args + #[allow(clippy::useless_vec)] pub fn dummy_type_id_script(&self) -> Script { Script::new_builder() .code_hash(self.code_hash.pack()) diff --git a/src/types/transaction_with_groups.rs b/src/types/transaction_with_groups.rs index 4f03dafb..c71646de 100644 --- a/src/types/transaction_with_groups.rs +++ b/src/types/transaction_with_groups.rs @@ -1,6 +1,8 @@ +use std::collections::HashMap; + use ckb_types::{ core::{ScriptHashType, TransactionView}, - packed::Script, + packed::{CellOutput, OutPoint, Script}, prelude::*, }; @@ -9,13 +11,19 @@ use crate::ScriptGroup; pub struct TransactionWithScriptGroups { pub(crate) tx_view: TransactionView, pub(crate) script_groups: Vec, + pub(crate) inputs: HashMap, } impl TransactionWithScriptGroups { - pub fn new(tx_view: TransactionView, script_groups: Vec) -> Self { + pub fn new( + tx_view: TransactionView, + script_groups: Vec, + inputs: HashMap, + ) -> Self { Self { tx_view, script_groups, + inputs, } } pub fn get_tx_view(&self) -> &TransactionView { @@ -39,6 +47,7 @@ impl TransactionWithScriptGroups { pub struct TransactionWithScriptGroupsBuilder { tx_view: Option, script_groups: Vec, + inputs: HashMap, } impl TransactionWithScriptGroupsBuilder { @@ -82,6 +91,7 @@ impl TransactionWithScriptGroupsBuilder { TransactionWithScriptGroups { tx_view: self.tx_view.unwrap(), script_groups: self.script_groups, + inputs: self.inputs, } } } diff --git a/src/unlock/omni_lock.rs b/src/unlock/omni_lock.rs index 721f10f7..1a08a234 100644 --- a/src/unlock/omni_lock.rs +++ b/src/unlock/omni_lock.rs @@ -1,28 +1,31 @@ use core::hash; +use std::convert::TryFrom; use std::fmt::Display; +use super::{MultisigConfig, OmniUnlockMode}; +use crate::util::{blake160, eos_auth, keccak160}; use crate::{ tx_builder::SinceSource, types::{ + cobuild::basic::Message, omni_lock::{Auth, Identity as IdentityType, IdentityOpt, OmniLockWitnessLock}, xudt_rce_mol::SmtProofEntryVec, }, + util::btc_auth, + Address, }; + +use bitflags::bitflags; +use ckb_types::packed::Script; +pub use ckb_types::prelude::Pack; use ckb_types::{ bytes::{BufMut, Bytes, BytesMut}, - packed::WitnessArgs, + packed::{Byte, WitnessArgs}, prelude::*, H160, H256, }; - -pub use ckb_types::prelude::Pack; use enum_repr_derive::{FromEnumToRepr, TryFromReprToEnum}; use serde::{de::Unexpected, Deserialize, Serialize}; -use std::convert::TryFrom; - -use bitflags::bitflags; - -use super::{MultisigConfig, OmniUnlockMode}; use thiserror::Error; #[derive( @@ -57,6 +60,12 @@ pub enum IdentityFlag { /// It follows the same unlocking method used by CKB MultiSig. Multisig = 6, + /// New ethereum with signing message with prefix + EthereumDisplaying = 18, + + /// It follows the same unlocking methods used by Solana. + Solana = 19, + /// The auth content that represents the blake160 hash of a lock script. /// The lock script will check if the current transaction contains an input cell with a matching lock script. /// Otherwise, it would return with an error. It's similar to P2SH in BTC. @@ -110,6 +119,33 @@ impl Identity { Self::new(IdentityFlag::OwnerLock, script_hash) } + /// Create an ethereum display omnilock with according script hash. + /// # Arguments + /// * `auth_content` keccak160 hash of public key + pub fn new_ethereum_display(auth_content: H160) -> Self { + Self::new(IdentityFlag::EthereumDisplaying, auth_content) + } + + /// Create an bitcoin omnilock with according script hash. + pub fn new_btc(auth_content: H160) -> Self { + Self::new(IdentityFlag::Bitcoin, auth_content) + } + + /// Create an eos omnilock with according script hash. + pub fn new_eos(auth_content: H160) -> Self { + Self::new(IdentityFlag::Eos, auth_content) + } + + /// Create an tron omnilock with according script hash. + pub fn new_tron(auth_content: H160) -> Self { + Self::new(IdentityFlag::Tron, auth_content) + } + + /// Create an dogcoin omnilock with according script hash. + pub fn new_dogcoin(auth_content: H160) -> Self { + Self::new(IdentityFlag::Dogecoin, auth_content) + } + /// convert the identify to smt_key. pub fn to_smt_key(&self) -> [u8; 32] { let mut ret = [0u8; 32]; @@ -133,10 +169,20 @@ impl Identity { return Err("Not enough bytes to parse".to_string()); } let flag = IdentityFlag::try_from(slice[0]) - .map_err(|e| format!("can't parse {} to valide IdentityFlat.", e))?; + .map_err(|e| format!("can't parse {} to validate IdentityFlag.", e))?; let auth_content = H160::from_slice(&slice[1..21]).map_err(|e| e.to_string())?; Ok(Identity { flag, auth_content }) } + + pub fn to_auth(&self) -> Auth { + let raw: [u8; 21] = self.clone().into(); + let builder = Auth::new_builder(); + let mut m_raw = [Byte::new(0); 21]; + for (i, v) in IntoIterator::into_iter(raw).enumerate() { + m_raw[i] = Byte::new(v); + } + builder.set(m_raw).build() + } } impl From for [u8; 21] { @@ -195,6 +241,66 @@ bitflags! { } } +#[derive(Serialize, Deserialize, Clone, Debug, Hash, PartialEq, Eq)] +pub struct ExecDlConfig { + preimage: Preimage, + signature_len: usize, +} + +impl ExecDlConfig { + pub fn new(preimage: Preimage, signature_len: usize) -> Self { + Self { + preimage, + signature_len, + } + } + + pub fn auth(&self) -> H160 { + self.preimage.auth() + } + + pub fn preimage(&self) -> &[u8] { + self.preimage.preimage() + } +} + +#[derive(Serialize, Deserialize, Clone, Debug, Hash, PartialEq, Eq)] +pub struct Preimage { + preimage: Vec, +} + +impl Preimage { + pub fn new_with_exec(script: Script, place: u8, bounds: [u8; 8], pubkey_hash: H160) -> Self { + let mut preimage = Vec::with_capacity(32 + 1 + 1 + 8 + 20); + + preimage.put(script.code_hash().as_slice()); + preimage.put(script.hash_type().as_slice()); + preimage.put_u8(place); + preimage.put(bounds.as_slice()); + preimage.put(pubkey_hash.as_bytes()); + + Self { preimage } + } + + pub fn new_with_dl(script: Script, pubkey_hash: H160) -> Self { + let mut preimage = Vec::with_capacity(32 + 1 + 20); + + preimage.put(script.code_hash().as_slice()); + preimage.put(script.hash_type().as_slice()); + preimage.put(pubkey_hash.as_bytes()); + + Self { preimage } + } + + pub fn auth(&self) -> H160 { + blake160(&self.preimage) + } + + pub fn preimage(&self) -> &[u8] { + &self.preimage + } +} + impl Serialize for SmtProofEntryVec { fn serialize(&self, serializer: S) -> Result where @@ -360,6 +466,16 @@ impl AdminConfig { rce_in_input, } } + + pub fn from_type_id(rc_type_id: H256) -> Self { + AdminConfig { + rc_type_id, + proofs: SmtProofEntryVec::default(), + auth: Identity::default(), + multisig_config: None, + rce_in_input: false, + } + } } #[derive(Error, Debug)] @@ -389,6 +505,16 @@ impl OmniLockAcpConfig { udt_minimum, } } + + pub fn from_slice(bytes: &[u8]) -> Result { + if bytes.len() < 2 { + return Err(format!("expect minimum 2 bytes, got {}", bytes.len())); + } + Ok(Self { + ckb_minimum: bytes[0], + udt_minimum: bytes[1], + }) + } } /// OmniLock configuration @@ -413,8 +539,16 @@ pub struct OmniLockConfig { acp_config: Option, /// 8 bytes since for time lock time_lock_config: Option, - // 32 bytes type script hash + // 32 bytes type script hash for supply mode info_cell: Option, + // Whether to cobuild + pub(crate) enable_cobuild: bool, + // cobuild message + pub(crate) cobuild_message: Option, + + pub(crate) btc_sign_vtype: BTCSignVtype, + + pub(crate) exec_dl_config: Option, } impl OmniLockConfig { @@ -425,6 +559,60 @@ impl OmniLockConfig { Self::new(IdentityFlag::PubkeyHash, lock_arg) } + pub fn from_slice(args: &[u8]) -> Result { + let id = Identity::from_slice(args)?; + let omni_lock_flags = OmniLockFlags::from_bits(args[21]).unwrap_or(OmniLockFlags::empty()); + let mut idx = 22; + let admin_config = if omni_lock_flags.contains(OmniLockFlags::ADMIN) { + let rc_type_id = H256::from_slice(&args[idx..]) + .map_err(|e| format!("parse admin type id error {}", e))?; + idx += 32; + Some(AdminConfig::from_type_id(rc_type_id)) + } else { + None + }; + let acp_config = if omni_lock_flags.contains(OmniLockFlags::ACP) { + let acp_config = OmniLockAcpConfig::from_slice(&args[idx..])?; + idx += 2; + Some(acp_config) + } else { + None + }; + let time_lock_config = if omni_lock_flags.contains(OmniLockFlags::TIME_LOCK) { + let mut time_bytes = [0u8; 8]; + time_bytes.copy_from_slice(&args[idx..idx + 8]); + idx += 8; + let time = u64::from_le_bytes(time_bytes); + Some(time) + } else { + None + }; + let info_cell = if omni_lock_flags.contains(OmniLockFlags::SUPPLY) { + let info_cell = H256::from_slice(&args[idx..]) + .map_err(|e| format!("parse admin type id error {}", e))?; + Some(info_cell) + } else { + None + }; + Ok(Self { + id, + multisig_config: None, + omni_lock_flags, + admin_config, + acp_config, + time_lock_config, + info_cell, + enable_cobuild: false, + cobuild_message: Some(Message::default().as_bytes()), + btc_sign_vtype: BTCSignVtype::P2PKHUncompressed, + exec_dl_config: None, + }) + } + + pub fn from_addr(addr: &Address) -> Result { + Self::from_slice(&addr.payload().args()) + } + pub fn new_multisig(multisig_config: MultisigConfig) -> Self { let blake160 = multisig_config.hash160(); OmniLockConfig { @@ -438,28 +626,57 @@ impl OmniLockConfig { acp_config: None, time_lock_config: None, info_cell: None, + enable_cobuild: false, + cobuild_message: Some(Message::default().as_bytes()), + btc_sign_vtype: BTCSignVtype::P2PKHUncompressed, + exec_dl_config: None, } } + /// Create an ethereum algorithm omnilock with pubkey /// /// # Arguments /// /// * `pubkey_hash` - a ehtereum address of an account. - /// - /// ``` - /// // pubkey is a public ethereum address - /// use ckb_sdk::unlock::OmniLockConfig; - /// use ckb_sdk::util::keccak160; - /// use ckb_crypto::secp::Pubkey; - /// - /// let pubkey = Pubkey::from([0u8; 64]); - /// let pubkey_hash = keccak160(pubkey.as_ref()); - /// let config = OmniLockConfig::new_ethereum(pubkey_hash); - /// ``` pub fn new_ethereum(pubkey_hash: H160) -> Self { Self::new(IdentityFlag::Ethereum, pubkey_hash) } + pub fn new_ethereum_from_pubkey(pubkey: &ckb_crypto::secp::Pubkey) -> Self { + let pubkey_hash = crate::util::keccak160(pubkey.as_ref()); + OmniLockConfig::new_ethereum(pubkey_hash) + } + + pub fn new_ethereum_display(pubkey_hash: H160) -> Self { + Self::new(IdentityFlag::EthereumDisplaying, pubkey_hash) + } + + pub fn new_btc_from_pubkey(pubkey: &ckb_crypto::secp::Pubkey, vtype: BTCSignVtype) -> Self { + let mut c = Self::new(IdentityFlag::Bitcoin, H160::from(btc_auth(pubkey, vtype))); + c.btc_sign_vtype = vtype; + c + } + + pub fn new_dogcoin_from_pubkey(pubkey: &ckb_crypto::secp::Pubkey, vtype: BTCSignVtype) -> Self { + let mut c = Self::new(IdentityFlag::Dogecoin, H160::from(btc_auth(pubkey, vtype))); + c.btc_sign_vtype = vtype; + c + } + + pub fn new_eos_from_pubkey(pubkey: &ckb_crypto::secp::Pubkey, vtype: BTCSignVtype) -> Self { + let mut c = Self::new(IdentityFlag::Eos, H160::from(eos_auth(pubkey, vtype))); + c.btc_sign_vtype = vtype; + c + } + + pub fn new_tron_from_pubkey(pubkey: &ckb_crypto::secp::Pubkey) -> Self { + Self::new(IdentityFlag::Tron, keccak160(pubkey.as_bytes())) + } + + pub fn new_solana_from_pubkey(pubkey: &ed25519_dalek::VerifyingKey) -> Self { + Self::new(IdentityFlag::Solana, blake160(pubkey.as_bytes())) + } + /// Create an ownerlock omnilock with according script hash. /// # Arguments /// * `script_hash` the proper blake160 hash of according ownerlock script. @@ -467,15 +684,22 @@ impl OmniLockConfig { Self::new(IdentityFlag::OwnerLock, script_hash) } + pub fn new_with_exec_preimage(config: ExecDlConfig) -> Self { + assert_eq!(config.preimage().len(), 32 + 1 + 1 + 8 + 20); + let mut c = Self::new(IdentityFlag::Exec, config.auth()); + c.exec_dl_config = Some(config); + c + } + + pub fn new_with_dl_preimage(config: ExecDlConfig) -> Self { + assert_eq!(config.preimage().len(), 32 + 1 + 20); + let mut c = Self::new(IdentityFlag::Dl, config.auth()); + c.exec_dl_config = Some(config); + c + } + /// Create a new OmniLockConfig pub fn new(flag: IdentityFlag, auth_content: H160) -> Self { - let auth_content = match flag { - IdentityFlag::PubkeyHash | IdentityFlag::Ethereum | IdentityFlag::OwnerLock => { - auth_content - } - _ => H160::from_slice(&[0; 20]).unwrap(), - }; - OmniLockConfig { id: Identity { flag, auth_content }, multisig_config: None, @@ -484,9 +708,28 @@ impl OmniLockConfig { acp_config: None, time_lock_config: None, info_cell: None, + enable_cobuild: false, + cobuild_message: Some(Message::default().as_bytes()), + btc_sign_vtype: BTCSignVtype::P2PKHUncompressed, + exec_dl_config: None, } } + /// Enable cobuild's build transaction standards, default is false + pub fn enable_cobuild(&mut self, enable: bool) { + self.enable_cobuild = enable + } + + /// Set cobuild message value, default is Some(Message::default) + pub fn cobuild_message(&mut self, message: Option) { + self.cobuild_message = message.map(|i| i.as_bytes()); + } + + /// Set btc sign vtype, defult is P2PKHUncompressed + pub fn btc_sign_vtype(&mut self, vtype: BTCSignVtype) { + self.btc_sign_vtype = vtype; + } + /// Set the admin cofiguration, and set the OmniLockFlags::ADMIN flag. /// # Arguments /// * `admin_config` The new admin config. @@ -535,6 +778,11 @@ impl OmniLockConfig { self.info_cell = None; } + /// Set multisignature config + pub fn set_multisig_config(&mut self, multisig_config: Option) { + self.multisig_config = multisig_config; + } + pub fn id(&self) -> &Identity { &self.id } @@ -647,7 +895,14 @@ impl OmniLockConfig { unlock_mode: OmniUnlockMode, ) -> Result { let mut builder = match self.id.flag { - IdentityFlag::PubkeyHash | IdentityFlag::Ethereum => OmniLockWitnessLock::new_builder() + IdentityFlag::PubkeyHash + | IdentityFlag::Ethereum + | IdentityFlag::EthereumDisplaying + | IdentityFlag::Bitcoin + | IdentityFlag::Dogecoin + | IdentityFlag::Eos + | IdentityFlag::Tron + | IdentityFlag::OwnerLock => OmniLockWitnessLock::new_builder() .signature(Some(Bytes::from(vec![0u8; 65])).pack()), IdentityFlag::Multisig => { let multisig_config = match unlock_mode { @@ -669,16 +924,28 @@ impl OmniLockConfig { omni_sig[..config_data.len()].copy_from_slice(&config_data); OmniLockWitnessLock::new_builder().signature(Some(Bytes::from(omni_sig)).pack()) } - IdentityFlag::OwnerLock => OmniLockWitnessLock::new_builder(), - _ => todo!("to support other placeholder_witness_lock implementions"), + IdentityFlag::Solana => OmniLockWitnessLock::new_builder() + .signature(Some(Bytes::from(vec![0u8; 96])).pack()), + IdentityFlag::Dl | IdentityFlag::Exec => { + let sig_len = self + .exec_dl_config + .as_ref() + .map(|c| c.signature_len) + .unwrap_or(65); + OmniLockWitnessLock::new_builder() + .signature(Some(Bytes::from(vec![0u8; sig_len])).pack()) + .preimage( + self.exec_dl_config + .as_ref() + .map(|i| Into::::into(i.preimage().to_vec())) + .pack(), + ) + } }; if unlock_mode == OmniUnlockMode::Admin { if let Some(config) = self.admin_config.as_ref() { - let mut temp = [0u8; 21]; - temp[0] = config.auth.flag as u8; - temp[1..21].copy_from_slice(config.auth.auth_content.as_bytes()); - let auth = Auth::from_slice(&temp).unwrap(); + let auth = config.auth.to_auth(); let ident = IdentityType::new_builder() .identity(auth) .proofs(config.proofs.clone()) @@ -704,24 +971,41 @@ impl OmniLockConfig { &self, unlock_mode: OmniUnlockMode, ) -> Result { - match self.id.flag { - IdentityFlag::PubkeyHash | IdentityFlag::Ethereum | IdentityFlag::Multisig => { - let lock = self.placeholder_witness_lock(unlock_mode)?; - Ok(WitnessArgs::new_builder().lock(Some(lock).pack()).build()) - } - IdentityFlag::OwnerLock => { - if self.admin_config.is_some() { - let lock = self.placeholder_witness_lock(unlock_mode)?; - Ok(WitnessArgs::new_builder().lock(Some(lock).pack()).build()) - } else { - Ok(WitnessArgs::default()) - } - } - _ => todo!("to support other placeholder_witness implementions"), + let lock = self.placeholder_witness_lock(unlock_mode)?; + Ok(WitnessArgs::new_builder().lock(Some(lock).pack()).build()) + } + + pub fn build_proofs(&self) -> SmtProofEntryVec { + if let Some(ref admin) = self.admin_config { + admin.proofs.clone() + } else { + Default::default() } } } +#[derive( + Clone, + Copy, + Serialize, + Deserialize, + Debug, + Hash, + Eq, + PartialEq, + TryFromReprToEnum, + FromEnumToRepr, +)] +#[repr(u8)] +#[derive(Default)] +pub enum BTCSignVtype { + #[default] + P2PKHUncompressed = 27, + P2PKHCompressed = 31, + SegwitP2SH = 35, + SegwitBech32 = 39, +} + #[cfg(test)] mod tests { use ckb_types::packed::Byte; diff --git a/src/unlock/signer.rs b/src/unlock/signer.rs index abc902b4..2ce535d9 100644 --- a/src/unlock/signer.rs +++ b/src/unlock/signer.rs @@ -1,9 +1,9 @@ use std::collections::HashSet; use anyhow::anyhow; -use ckb_hash::{blake2b_256, new_blake2b}; +use ckb_hash::{blake2b_256, new_blake2b, Blake2b, Blake2bBuilder}; use ckb_types::{ - bytes::{Bytes, BytesMut}, + bytes::{BufMut, Bytes, BytesMut}, core::{ScriptHashType, TransactionView}, error::VerificationError, packed::{self, BytesOpt, Script, WitnessArgs}, @@ -11,22 +11,32 @@ use ckb_types::{ H160, }; use serde::{Deserialize, Serialize}; +use sha3::{Digest, Keccak256}; use thiserror::Error; -use crate::{constants::MULTISIG_TYPE_HASH, types::omni_lock::OmniLockWitnessLock}; use crate::{ - traits::{Signer, SignerError}, - util::convert_keccak256_hash, + constants::{COMMON_PREFIX, MULTISIG_TYPE_HASH}, + types::{cobuild::top_level::WitnessLayoutUnion, omni_lock::OmniLockWitnessLock}, + util::{btc_convert_message, btc_convert_sign}, }; use crate::{ - types::{AddressPayload, CodeHashIndex, ScriptGroup, Since}, + traits::{Signer, SignerError, TransactionDependencyProvider}, + util::{calculate_sha256, convert_keccak256_hash, hex_encode}, +}; +use crate::{ + types::{ + cobuild::{ + basic::{Message, SighashAll, SighashAllOnly}, + top_level::WitnessLayout, + }, + omni_lock, + xudt_rce_mol::SmtProofEntryVec, + AddressPayload, CodeHashIndex, ScriptGroup, Since, + }, Address, NetworkType, }; -use super::{ - omni_lock::{ConfigError, Identity}, - IdentityFlag, OmniLockConfig, -}; +use super::{omni_lock::ConfigError, IdentityFlag, OmniLockConfig}; #[derive(Error, Debug)] pub enum ScriptSignError { @@ -67,6 +77,7 @@ pub trait ScriptSigner { &self, tx: &TransactionView, script_group: &ScriptGroup, + _tx_dep_provider: &dyn TransactionDependencyProvider, ) -> Result; } @@ -131,6 +142,7 @@ impl ScriptSigner for SecpSighashScriptSigner { &self, tx: &TransactionView, script_group: &ScriptGroup, + _tx_dep_provider: &dyn TransactionDependencyProvider, ) -> Result { let args = script_group.script.args().raw_data(); self.sign_tx_with_owner_id(args.as_ref(), tx, script_group) @@ -230,12 +242,16 @@ impl MultisigConfig { } pub fn placeholder_witness(&self) -> WitnessArgs { + WitnessArgs::new_builder() + .lock(self.placeholder_witness_lock().pack()) + .build() + } + + pub fn placeholder_witness_lock(&self) -> Option { let config_data = self.to_witness_data(); let mut zero_lock = vec![0u8; config_data.len() + 65 * self.threshold() as usize]; zero_lock[0..config_data.len()].copy_from_slice(config_data.as_ref()); - WitnessArgs::new_builder() - .lock(Some(Bytes::from(zero_lock)).pack()) - .build() + Some(Bytes::from(zero_lock)) } pub fn to_address(&self, network: NetworkType, since_absolute_epoch: Option) -> Address { @@ -292,6 +308,7 @@ impl ScriptSigner for SecpMultisigScriptSigner { &self, tx: &TransactionView, script_group: &ScriptGroup, + _tx_dep_provider: &dyn TransactionDependencyProvider, ) -> Result { let witness_idx = script_group.input_indices[0]; let mut witnesses: Vec = tx.witnesses().into_iter().collect(); @@ -384,6 +401,7 @@ impl ScriptSigner for AcpScriptSigner { &self, tx: &TransactionView, script_group: &ScriptGroup, + _tx_dep_provider: &dyn TransactionDependencyProvider, ) -> Result { let args = script_group.script.args().raw_data(); let id = &args[0..20]; @@ -433,6 +451,7 @@ impl ScriptSigner for ChequeScriptSigner { &self, tx: &TransactionView, script_group: &ScriptGroup, + _tx_dep_provider: &dyn TransactionDependencyProvider, ) -> Result { let args = script_group.script.args().raw_data(); let id = self.owner_id(args.as_ref()); @@ -441,6 +460,66 @@ impl ScriptSigner for ChequeScriptSigner { } } +pub const PERSONALIZATION_SIGHASH_ALL: &[u8] = b"ckb-tcob-sighash"; +pub const PERSONALIZATION_SIGHASH_ALL_ONLY: &[u8] = b"ckb-tcob-sgohash"; + +/// return a blake2b instance with personalization for SighashAll +pub fn new_sighash_all_blake2b() -> Blake2b { + Blake2bBuilder::new(32) + .personal(PERSONALIZATION_SIGHASH_ALL) + .build() +} + +/// return a blake2b instance with personalization for SighashAllOnly +pub fn new_sighash_all_only_blake2b() -> Blake2b { + Blake2bBuilder::new(32) + .personal(PERSONALIZATION_SIGHASH_ALL_ONLY) + .build() +} + +pub fn cobuild_generate_signing_message_hash( + message: &Option, + tx_dep_provider: &dyn TransactionDependencyProvider, + tx: &TransactionView, +) -> Bytes { + // message + let mut hasher = match message { + Some(m) => { + let mut hasher = new_sighash_all_blake2b(); + hasher.update(m); + hasher + } + None => new_sighash_all_only_blake2b(), + }; + // tx hash + hasher.update(tx.hash().as_slice()); + + // inputs cell and data + let inputs_len = tx.inputs().len(); + for i in 0..inputs_len { + let input_cell = tx.inputs().get(i).unwrap(); + let input_cell_out_point = input_cell.previous_output(); + let (input_cell, input_cell_data) = ( + tx_dep_provider.get_cell(&input_cell_out_point).unwrap(), + tx_dep_provider + .get_cell_data(&input_cell_out_point) + .unwrap(), + ); + hasher.update(input_cell.as_slice()); + hasher.update(&(input_cell_data.len() as u32).to_le_bytes()); + hasher.update(&input_cell_data); + } + // extra witnesses + for witness in tx.witnesses().into_iter().skip(inputs_len) { + hasher.update(&(witness.len() as u32).to_le_bytes()); + hasher.update(&witness.raw_data()); + } + + let mut result = vec![0u8; 32]; + hasher.finalize(&mut result); + Bytes::from(result) +} + /// Common logic of generate message for certain script group. Overwrite /// this method to support special use case. pub fn generate_message( @@ -550,22 +629,10 @@ impl OmniLockScriptSigner { fn sign_multisig_tx( &self, tx: &TransactionView, - script_group: &ScriptGroup, - ) -> Result { - let witness_idx = script_group.input_indices[0]; - let mut witnesses: Vec = tx.witnesses().into_iter().collect(); - while witnesses.len() <= witness_idx { - witnesses.push(Default::default()); - } - let tx_new = tx - .as_advanced_builder() - .set_witnesses(witnesses.clone()) - .build(); - - let zero_lock = self.config.zero_lock(self.unlock_mode)?; - let zero_lock_len = zero_lock.len(); - let message = generate_message(&tx_new, script_group, zero_lock)?; - + message: Bytes, + witness: Bytes, + cobuild: bool, + ) -> Result { let multisig_config = match self.unlock_mode { OmniUnlockMode::Admin => self .config @@ -581,38 +648,47 @@ impl OmniLockScriptSigner { .filter(|id| self.signer.match_id(id.as_bytes())) .map(|id| self.signer.sign(id.as_bytes(), message.as_ref(), true, tx)) .collect::, SignerError>>()?; - // Put signature into witness - let witness_idx = script_group.input_indices[0]; - let witness_data = witnesses[witness_idx].raw_data(); - let mut current_witness: WitnessArgs = if witness_data.is_empty() { - WitnessArgs::default() - } else { - WitnessArgs::from_slice(witness_data.as_ref())? - }; - let lock_field = current_witness.lock().to_opt().map(|data| data.raw_data()); - let omnilock_witnesslock = if let Some(lock_field) = lock_field { - if lock_field.len() != zero_lock_len { - return Err(ScriptSignError::Other(anyhow!( - "invalid witness lock field length: {}, expected: {}", - lock_field.len(), - zero_lock_len, - ))); - } - OmniLockWitnessLock::from_slice(lock_field.as_ref())? + let config_data = multisig_config.to_witness_data(); + + let mut omni_sig = if cobuild { + let mut omni_sig = + vec![0u8; config_data.len() + multisig_config.threshold() as usize * 65]; + omni_sig[..config_data.len()].copy_from_slice(&config_data); + omni_sig } else { - OmniLockWitnessLock::default() + // use current_witness to recover data is to be compatible with builder mode. which sign transaction for multiple times + // In transaction mode, there is no need to do this. + let current_witness: WitnessArgs = if witness.is_empty() { + WitnessArgs::default() + } else { + WitnessArgs::from_slice(witness.as_ref())? + }; + let lock_field = current_witness.lock().to_opt().map(|data| data.raw_data()); + let omnilock_witnesslock = if let Some(lock_field) = lock_field { + let zero_lock_len = self.config.zero_lock(self.unlock_mode)?.len(); + if lock_field.len() != zero_lock_len { + return Err(ScriptSignError::Other(anyhow!( + "invalid witness lock field length: {}, expected: {}", + lock_field.len(), + zero_lock_len, + ))); + } + OmniLockWitnessLock::from_slice(lock_field.as_ref())? + } else { + OmniLockWitnessLock::default() + }; + omnilock_witnesslock + .signature() + .to_opt() + .map(|data| data.raw_data().as_ref().to_vec()) + .unwrap_or_else(|| { + let mut omni_sig = + vec![0u8; config_data.len() + multisig_config.threshold() as usize * 65]; + omni_sig[..config_data.len()].copy_from_slice(&config_data); + omni_sig + }) }; - let config_data = multisig_config.to_witness_data(); - let mut omni_sig = omnilock_witnesslock - .signature() - .to_opt() - .map(|data| data.raw_data().as_ref().to_vec()) - .unwrap_or_else(|| { - let mut omni_sig = - vec![0u8; config_data.len() + multisig_config.threshold() as usize * 65]; - omni_sig[..config_data.len()].copy_from_slice(&config_data); - omni_sig - }); + for signature in signatures { let mut idx = config_data.len(); while idx < omni_sig.len() { @@ -629,59 +705,18 @@ impl OmniLockScriptSigner { return Err(ScriptSignError::TooManySignatures); } } - let lock = omnilock_witnesslock - .as_builder() - .signature(Some(Bytes::from(omni_sig)).pack()) - .build() - .as_bytes(); - - current_witness = current_witness.as_builder().lock(Some(lock).pack()).build(); - witnesses[witness_idx] = current_witness.as_bytes().pack(); - Ok(tx.as_advanced_builder().set_witnesses(witnesses).build()) - } - - fn sign_ethereum_tx( - &self, - tx: &TransactionView, - script_group: &ScriptGroup, - id: &Identity, - ) -> Result { - let witness_idx = script_group.input_indices[0]; - let mut witnesses: Vec = tx.witnesses().into_iter().collect(); - while witnesses.len() <= witness_idx { - witnesses.push(Default::default()); - } - let tx_new = tx - .as_advanced_builder() - .set_witnesses(witnesses.clone()) - .build(); - - let zero_lock = self.config.zero_lock(self.unlock_mode())?; - let message = generate_message(&tx_new, script_group, zero_lock)?; - let message = convert_keccak256_hash(message.as_ref()); - - let signature = self - .signer - .sign(id.auth_content().as_ref(), message.as_ref(), true, tx)?; - - // Put signature into witness - let witness_data = witnesses[witness_idx].raw_data(); - let mut current_witness: WitnessArgs = if witness_data.is_empty() { - WitnessArgs::default() - } else { - WitnessArgs::from_slice(witness_data.as_ref())? - }; - - let lock = Self::build_witness_lock(current_witness.lock(), signature)?; - current_witness = current_witness.as_builder().lock(Some(lock).pack()).build(); - witnesses[witness_idx] = current_witness.as_bytes().pack(); - Ok(tx.as_advanced_builder().set_witnesses(witnesses).build()) + Ok(Bytes::from(omni_sig)) } /// Build proper witness lock pub fn build_witness_lock( orig_lock: BytesOpt, signature: Bytes, + use_rc: bool, + use_rc_identity: bool, + proofs: &SmtProofEntryVec, + identity: &omni_lock::Auth, + preimage: Option, ) -> Result { let lock_field = orig_lock.to_opt().map(|data| data.raw_data()); let omnilock_witnesslock = if let Some(lock_field) = lock_field { @@ -690,11 +725,24 @@ impl OmniLockScriptSigner { OmniLockWitnessLock::default() }; - Ok(omnilock_witnesslock - .as_builder() - .signature(Some(signature).pack()) - .build() - .as_bytes()) + let builder = omnilock_witnesslock.as_builder(); + + let mut builder = builder.signature(Some(signature).pack()); + + if builder.preimage.is_none() { + builder = builder.preimage(preimage.pack()); + } + + if use_rc && use_rc_identity && builder.omni_identity.is_none() { + let rc_identity = omni_lock::IdentityBuilder::default() + .identity(identity.clone()) + .proofs(proofs.clone()) + .build(); + let opt = omni_lock::IdentityOpt::new_unchecked(rc_identity.as_bytes()); + builder = builder.omni_identity(opt); + } + + Ok(builder.build().as_bytes()) } } @@ -730,7 +778,16 @@ impl ScriptSigner for OmniLockScriptSigner { return false; } match self.config.id().flag() { - IdentityFlag::PubkeyHash | IdentityFlag::Ethereum => self + IdentityFlag::PubkeyHash + | IdentityFlag::Ethereum + | IdentityFlag::Bitcoin + | IdentityFlag::Dogecoin + | IdentityFlag::EthereumDisplaying + | IdentityFlag::Tron + | IdentityFlag::Solana + | IdentityFlag::Eos + | IdentityFlag::Dl + | IdentityFlag::Exec => self .signer .match_id(self.config.id().auth_content().as_ref()), IdentityFlag::Multisig => { @@ -748,7 +805,6 @@ impl ScriptSigner for OmniLockScriptSigner { // should not reach here, return true for compatible reason true } - _ => todo!("other auth type not supported yet"), } } @@ -756,6 +812,7 @@ impl ScriptSigner for OmniLockScriptSigner { &self, tx: &TransactionView, script_group: &ScriptGroup, + tx_dep_provider: &dyn TransactionDependencyProvider, ) -> Result { let id = match self.unlock_mode { OmniUnlockMode::Admin => self @@ -766,49 +823,189 @@ impl ScriptSigner for OmniLockScriptSigner { .clone(), OmniUnlockMode::Normal => self.config.id().clone(), }; - match id.flag() { - IdentityFlag::PubkeyHash => { - let witness_idx = script_group.input_indices[0]; - let mut witnesses: Vec = tx.witnesses().into_iter().collect(); - while witnesses.len() <= witness_idx { - witnesses.push(Default::default()); - } - let tx_new = tx - .as_advanced_builder() - .set_witnesses(witnesses.clone()) - .build(); - let zero_lock = self.config.zero_lock(self.unlock_mode)?; - let message = generate_message(&tx_new, script_group, zero_lock)?; + let witness_idx = script_group.input_indices[0]; + let mut witnesses: Vec = tx.witnesses().into_iter().collect(); + while witnesses.len() <= witness_idx { + witnesses.push(Default::default()); + } + let tx_new = tx + .as_advanced_builder() + .set_witnesses(witnesses.clone()) + .build(); + + let message = if self.config.enable_cobuild { + cobuild_generate_signing_message_hash(&self.config.cobuild_message, tx_dep_provider, tx) + } else { + let zero_lock = self.config.zero_lock(self.unlock_mode)?; + generate_message(&tx_new, script_group, zero_lock)? + }; + let signature = match id.flag() { + IdentityFlag::PubkeyHash => { + self.signer + .sign(id.auth_content().as_ref(), message.as_ref(), true, tx)? + } + IdentityFlag::OwnerLock => Bytes::from(vec![0; 65]), + IdentityFlag::Ethereum => { + let message = convert_keccak256_hash(message.as_ref()); - let signature = + self.signer + .sign(id.auth_content().as_ref(), message.as_ref(), true, tx)? + } + IdentityFlag::Multisig => self.sign_multisig_tx( + tx, + message, + witnesses[witness_idx].raw_data(), + self.config.enable_cobuild, + )?, + IdentityFlag::Bitcoin => { + let msg = btc_convert_message(&message); + + let sign = self.signer - .sign(id.auth_content().as_ref(), message.as_ref(), true, tx)?; - - // Put signature into witness - let witness_data = witnesses[witness_idx].raw_data(); - let mut current_witness: WitnessArgs = if witness_data.is_empty() { - WitnessArgs::default() - } else { - WitnessArgs::from_slice(witness_data.as_ref())? - }; + .sign(id.auth_content().as_ref(), msg.as_slice(), true, tx)?; + btc_convert_sign(sign, self.config.btc_sign_vtype) + } + IdentityFlag::EthereumDisplaying => { + let eth_prefix = b"\x19Ethereum Signed Message:\n"; + let mut hasher = Keccak256::new(); + hasher.update(eth_prefix); + hasher.update(Bytes::from(format!( + "{}", + COMMON_PREFIX.len() + message.len() * 2 + ))); + hasher.update(Bytes::from(COMMON_PREFIX)); - let lock = Self::build_witness_lock(current_witness.lock(), signature)?; + hasher.update(hex_encode(&message)); + let r = hasher.finalize(); - current_witness = current_witness.as_builder().lock(Some(lock).pack()).build(); - witnesses[witness_idx] = current_witness.as_bytes().pack(); - Ok(tx.as_advanced_builder().set_witnesses(witnesses).build()) + self.signer + .sign(id.auth_content().as_ref(), r.as_slice(), true, tx)? } - IdentityFlag::Ethereum => self.sign_ethereum_tx(tx, script_group, &id), - IdentityFlag::Multisig => self.sign_multisig_tx(tx, script_group), - IdentityFlag::OwnerLock => { - // should not reach here, just return a clone for compatible reason. - Ok(tx.clone()) + IdentityFlag::Tron => { + let tron_prefix: &[u8; 24] = b"\x19TRON Signed Message:\n32"; + let mut hasher = Keccak256::new(); + hasher.update(tron_prefix); + hasher.update(message); + let r = hasher.finalize(); + self.signer + .sign(id.auth_content().as_ref(), r.as_slice(), true, tx)? } - _ => { - todo!("not supported yet"); + IdentityFlag::Eos => { + let sign = self + .signer + .sign(id.auth_content().as_ref(), &message, true, tx)?; + btc_convert_sign(sign, self.config.btc_sign_vtype) } + IdentityFlag::Dogecoin => { + let message_magic = b"\x19Dogecoin Signed Message:\n\x40"; + let msg_hex = hex_encode(&message); + assert_eq!(msg_hex.len(), 64); + + let mut temp2 = Vec::with_capacity(message_magic.len() + msg_hex.len()); + temp2.put(message_magic.as_slice()); + temp2.put(msg_hex.as_bytes()); + + let msg = calculate_sha256(&temp2); + let r = calculate_sha256(&msg); + let sign = self + .signer + .sign(id.auth_content().as_ref(), r.as_slice(), true, tx)?; + btc_convert_sign(sign, self.config.btc_sign_vtype) + } + IdentityFlag::Solana => { + // should we impl phantom signature mode? + let msg = { + let mut prefix = b"CKB transaction: 0x".to_vec(); + prefix.extend(hex_encode(&message).as_bytes()); + prefix + }; + self.signer + .sign(id.auth_content().as_ref(), msg.as_slice(), true, tx)? + } + IdentityFlag::Dl | IdentityFlag::Exec => { + self.signer + .sign(id.auth_content().as_ref(), &message, true, tx)? + } + }; + if self.config.enable_cobuild { + let witness_data = witnesses[witness_idx].raw_data(); + let current_witness: WitnessLayout = if witness_data.is_empty() { + WitnessLayout::default() + } else { + WitnessLayout::from_slice(witness_data.as_ref())? + }; + + let lock_field = match current_witness.to_enum() { + WitnessLayoutUnion::SighashAll(s) => { + OmniLockWitnessLock::from_slice(&s.as_reader().seal().raw_data()[1..]) + .unwrap() + .as_bytes() + } + WitnessLayoutUnion::SighashAllOnly(s) => { + OmniLockWitnessLock::from_slice(&s.as_reader().seal().raw_data()[1..]) + .unwrap() + .as_bytes() + } + _ => panic!("not support yet"), + }; + let lock = Self::build_witness_lock( + Some(lock_field).pack(), + signature, + self.config.use_rc(), + matches!(self.unlock_mode, OmniUnlockMode::Admin), + &self.config.build_proofs(), + &id.to_auth(), + self.config + .exec_dl_config + .clone() + .map(|i| i.preimage().to_vec().into()), + )?; + match &self.config.cobuild_message { + Some(msg) => { + let sighash_all = SighashAll::new_builder() + .message(Message::new_unchecked(msg.clone())) + .seal([Bytes::copy_from_slice(&[0x00u8]), lock].concat().pack()) + .build(); + let sighash_all = WitnessLayout::new_builder().set(sighash_all).build(); + let sighash_all = sighash_all.as_bytes(); + witnesses[witness_idx] = sighash_all.pack(); + } + None => { + let sighash_all_only = SighashAllOnly::new_builder() + .seal([Bytes::copy_from_slice(&[0x00u8]), lock].concat().pack()) + .build(); + let sighash_all_only = + WitnessLayout::new_builder().set(sighash_all_only).build(); + let sighash_all_only = sighash_all_only.as_bytes(); + witnesses[witness_idx] = sighash_all_only.pack(); + } + } + } else { + // Put signature into witness + let witness_data = witnesses[witness_idx].raw_data(); + let mut current_witness: WitnessArgs = if witness_data.is_empty() { + WitnessArgs::default() + } else { + WitnessArgs::from_slice(witness_data.as_ref())? + }; + + let lock = Self::build_witness_lock( + current_witness.lock(), + signature, + self.config.use_rc(), + matches!(self.unlock_mode, OmniUnlockMode::Admin), + &self.config.build_proofs(), + &id.to_auth(), + self.config + .exec_dl_config + .clone() + .map(|i| i.preimage().to_vec().into()), + )?; + current_witness = current_witness.as_builder().lock(Some(lock).pack()).build(); + witnesses[witness_idx] = current_witness.as_bytes().pack(); } + Ok(tx.as_advanced_builder().set_witnesses(witnesses).build()) } } diff --git a/src/unlock/unlocker.rs b/src/unlock/unlocker.rs index 9ff74160..3cb88ad6 100644 --- a/src/unlock/unlocker.rs +++ b/src/unlock/unlocker.rs @@ -91,24 +91,30 @@ pub fn fill_witness_lock( tx: &TransactionView, script_group: &ScriptGroup, lock_field: Bytes, + enable_cobuild: bool, ) -> Result { let witness_idx = script_group.input_indices[0]; let mut witnesses: Vec = tx.witnesses().into_iter().collect(); while witnesses.len() <= witness_idx { witnesses.push(Default::default()); } - let witness_data = witnesses[witness_idx].raw_data(); - let mut witness = if witness_data.is_empty() { - WitnessArgs::default() + + if enable_cobuild { + Ok(tx.clone()) } else { - WitnessArgs::from_slice(witness_data.as_ref()) - .map_err(|_| UnlockError::InvalidWitnessArgs(witness_idx))? - }; - if witness.lock().is_none() { - witness = witness.as_builder().lock(Some(lock_field).pack()).build(); + let witness_data = witnesses[witness_idx].raw_data(); + let mut witness = if witness_data.is_empty() { + WitnessArgs::default() + } else { + WitnessArgs::from_slice(witness_data.as_ref()) + .map_err(|_| UnlockError::InvalidWitnessArgs(witness_idx))? + }; + if witness.lock().is_none() { + witness = witness.as_builder().lock(Some(lock_field).pack()).build(); + witnesses[witness_idx] = witness.as_bytes().pack(); + } + Ok(tx.as_advanced_builder().set_witnesses(witnesses).build()) } - witnesses[witness_idx] = witness.as_bytes().pack(); - Ok(tx.as_advanced_builder().set_witnesses(witnesses).build()) } pub fn reset_witness_lock( @@ -160,9 +166,9 @@ impl ScriptUnlocker for SecpSighashUnlocker { &self, tx: &TransactionView, script_group: &ScriptGroup, - _tx_dep_provider: &dyn TransactionDependencyProvider, + tx_dep_provider: &dyn TransactionDependencyProvider, ) -> Result { - Ok(self.signer.sign_tx(tx, script_group)?) + Ok(self.signer.sign_tx(tx, script_group, tx_dep_provider)?) } fn fill_placeholder_witness( @@ -171,7 +177,7 @@ impl ScriptUnlocker for SecpSighashUnlocker { script_group: &ScriptGroup, _tx_dep_provider: &dyn TransactionDependencyProvider, ) -> Result { - fill_witness_lock(tx, script_group, Bytes::from(vec![0u8; 65])) + fill_witness_lock(tx, script_group, Bytes::from(vec![0u8; 65]), false) } } @@ -197,9 +203,9 @@ impl ScriptUnlocker for SecpMultisigUnlocker { &self, tx: &TransactionView, script_group: &ScriptGroup, - _tx_dep_provider: &dyn TransactionDependencyProvider, + tx_dep_provider: &dyn TransactionDependencyProvider, ) -> Result { - Ok(self.signer.sign_tx(tx, script_group)?) + Ok(self.signer.sign_tx(tx, script_group, tx_dep_provider)?) } fn fill_placeholder_witness( @@ -212,7 +218,7 @@ impl ScriptUnlocker for SecpMultisigUnlocker { let config_data = config.to_witness_data(); let mut zero_lock = vec![0u8; config_data.len() + 65 * (config.threshold() as usize)]; zero_lock[0..config_data.len()].copy_from_slice(&config_data); - fill_witness_lock(tx, script_group, Bytes::from(zero_lock)) + fill_witness_lock(tx, script_group, Bytes::from(zero_lock), false) } } @@ -434,7 +440,7 @@ impl ScriptUnlocker for AcpUnlocker { if self.is_unlocked(tx, script_group, tx_dep_provider)? { self.clear_placeholder_witness(tx, script_group) } else { - Ok(self.signer.sign_tx(tx, script_group)?) + Ok(self.signer.sign_tx(tx, script_group, tx_dep_provider)?) } } @@ -456,7 +462,7 @@ impl ScriptUnlocker for AcpUnlocker { if self.is_unlocked(tx, script_group, tx_dep_provider)? { Ok(tx.clone()) } else { - fill_witness_lock(tx, script_group, Bytes::from(vec![0u8; 65])) + fill_witness_lock(tx, script_group, Bytes::from(vec![0u8; 65]), false) } } } @@ -580,7 +586,7 @@ impl ScriptUnlocker for ChequeUnlocker { if self.is_unlocked(tx, script_group, tx_dep_provider)? { self.clear_placeholder_witness(tx, script_group) } else { - Ok(self.signer.sign_tx(tx, script_group)?) + Ok(self.signer.sign_tx(tx, script_group, tx_dep_provider)?) } } @@ -602,7 +608,7 @@ impl ScriptUnlocker for ChequeUnlocker { if self.is_unlocked(tx, script_group, tx_dep_provider)? { Ok(tx.clone()) } else { - fill_witness_lock(tx, script_group, Bytes::from(vec![0u8; 65])) + fill_witness_lock(tx, script_group, Bytes::from(vec![0u8; 65]), false) } } } @@ -709,9 +715,9 @@ impl ScriptUnlocker for OmniLockUnlocker { &self, tx: &TransactionView, script_group: &ScriptGroup, - _tx_dep_provider: &dyn TransactionDependencyProvider, + tx_dep_provider: &dyn TransactionDependencyProvider, ) -> Result { - Ok(self.signer.sign_tx(tx, script_group)?) + Ok(self.signer.sign_tx(tx, script_group, tx_dep_provider)?) } fn fill_placeholder_witness( @@ -722,7 +728,7 @@ impl ScriptUnlocker for OmniLockUnlocker { ) -> Result { let config = self.signer.config(); let lock_field = config.placeholder_witness_lock(self.signer.unlock_mode())?; - fill_witness_lock(tx, script_group, lock_field) + fill_witness_lock(tx, script_group, lock_field, config.enable_cobuild) } } #[cfg(test)] diff --git a/src/util.rs b/src/util.rs index 9148f4ab..0ebf7b83 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,6 +1,9 @@ use std::{convert::TryInto, ptr, sync::atomic}; +use bytes::{BufMut, Bytes, BytesMut}; +use ckb_crypto::secp::Pubkey; use ckb_dao_utils::extract_dao_data; +use ckb_hash::blake2b_256; use ckb_types::{ core::{Capacity, EpochNumber, EpochNumberWithFraction, HeaderView}, packed::CellOutput, @@ -9,8 +12,12 @@ use ckb_types::{ }; use sha3::{Digest, Keccak256}; -use crate::rpc::CkbRpcClient; use crate::traits::LiveCell; +use crate::{ + constants::{BTC_PREFIX, SECP256K1_TAG_PUBKEY_UNCOMPRESSED}, + rpc::CkbRpcClient, + unlock::omni_lock::BTCSignVtype, +}; use secp256k1::ffi::CPtr; @@ -156,6 +163,105 @@ pub fn convert_keccak256_hash(message: &[u8]) -> H256 { H256::from_slice(r.as_slice()).expect("convert_keccak256_hash") } +pub fn calculate_sha256(buf: &[u8]) -> [u8; 32] { + use sha2::Sha256; + + let mut c = Sha256::new(); + c.update(buf); + c.finalize().into() +} + +pub fn calculate_ripemd160(buf: &[u8]) -> [u8; 20] { + use ripemd::Ripemd160; + + let mut hasher = Ripemd160::new(); + hasher.update(buf); + let buf = hasher.finalize().to_vec(); + + buf.try_into().unwrap() +} + +pub fn bitcoin_hash160(buf: &[u8]) -> [u8; 20] { + calculate_ripemd160(&calculate_sha256(buf)) +} + +pub fn btc_auth(pubkey: &Pubkey, vtype: BTCSignVtype) -> [u8; 20] { + match vtype { + BTCSignVtype::P2PKHUncompressed => { + let mut pk_data = vec![0; 65]; + pk_data[0] = SECP256K1_TAG_PUBKEY_UNCOMPRESSED; + pk_data[1..].copy_from_slice(pubkey.as_bytes()); + + bitcoin_hash160(&pk_data) + } + BTCSignVtype::P2PKHCompressed | BTCSignVtype::SegwitBech32 => { + bitcoin_hash160(&pubkey.serialize()) + } + BTCSignVtype::SegwitP2SH => { + let mut pk_data = vec![0; 22]; + pk_data[0] = 0; + pk_data[1] = 20; + pk_data[2..].copy_from_slice(&bitcoin_hash160(&pubkey.serialize())); + bitcoin_hash160(&pk_data) + } + } +} + +pub fn eos_auth(pubkey: &Pubkey, vtype: BTCSignVtype) -> [u8; 20] { + let buf = match vtype { + BTCSignVtype::P2PKHUncompressed => { + let mut temp = Vec::with_capacity(65); + temp.put_u8(SECP256K1_TAG_PUBKEY_UNCOMPRESSED); + temp.put(pubkey.as_bytes()); + temp + } + BTCSignVtype::P2PKHCompressed => pubkey.serialize(), + _ => panic!("unsupport vtype"), + }; + blake2b_256(buf)[..20].try_into().unwrap() +} + +pub fn btc_convert_message(msg: &[u8]) -> [u8; 32] { + let message_magic = b"Bitcoin Signed Message:\n"; + let msg_hex = hex_encode(msg); + assert_eq!(msg_hex.len(), 64); + + let mut temp2 = + Vec::with_capacity(1 + message_magic.len() + 1 + BTC_PREFIX.len() + msg_hex.len()); + temp2.put_u8(message_magic.len() as u8); + temp2.put(message_magic.as_slice()); + + temp2.put_u8(0x40 + BTC_PREFIX.len() as u8); + + temp2.put(format!("{}{}", BTC_PREFIX, msg_hex).as_bytes()); + + let msg = calculate_sha256(&temp2); + calculate_sha256(&msg) +} + +pub fn btc_convert_sign(sign: Bytes, vtype: BTCSignVtype) -> Bytes { + let recid = sign[64]; + + let mark = recid + vtype as u8; + + let mut ret = BytesMut::with_capacity(65); + ret.put_u8(mark); + ret.put(&sign[0..64]); + ret.freeze() +} + +pub fn hex_encode(message: &[u8]) -> String { + let mut res = vec![0; message.len() * 2]; + faster_hex::hex_encode(message, &mut res).unwrap(); + String::from_utf8(res).unwrap() +} + +pub fn hex_decode(message: &[u8]) -> Vec { + let mut res = vec![0; message.len() / 2]; + faster_hex::hex_decode(message, &mut res).unwrap(); + res +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/test-data/Makefile b/test-data/Makefile similarity index 100% rename from src/test-data/Makefile rename to test-data/Makefile diff --git a/src/test-data/always_success b/test-data/always_success similarity index 100% rename from src/test-data/always_success rename to test-data/always_success diff --git a/test-data/always_success_dl b/test-data/always_success_dl new file mode 100755 index 00000000..0113a2e4 Binary files /dev/null and b/test-data/always_success_dl differ diff --git a/src/test-data/anyone_can_pay b/test-data/anyone_can_pay similarity index 100% rename from src/test-data/anyone_can_pay rename to test-data/anyone_can_pay diff --git a/src/test-data/ckb-cheque-script b/test-data/ckb-cheque-script similarity index 100% rename from src/test-data/ckb-cheque-script rename to test-data/ckb-cheque-script diff --git a/src/test-data/ckb_syscalls.h b/test-data/ckb_syscalls.h similarity index 100% rename from src/test-data/ckb_syscalls.h rename to test-data/ckb_syscalls.h diff --git a/src/test-data/cycle b/test-data/cycle similarity index 100% rename from src/test-data/cycle rename to test-data/cycle diff --git a/src/test-data/cycle.c b/test-data/cycle.c similarity index 100% rename from src/test-data/cycle.c rename to test-data/cycle.c diff --git a/src/test-data/cycle.debug b/test-data/cycle.debug similarity index 100% rename from src/test-data/cycle.debug rename to test-data/cycle.debug diff --git a/src/test-data/cycle.md b/test-data/cycle.md similarity index 100% rename from src/test-data/cycle.md rename to test-data/cycle.md diff --git a/src/test-data/genesis_block.json b/test-data/genesis_block.json similarity index 100% rename from src/test-data/genesis_block.json rename to test-data/genesis_block.json diff --git a/test-data/omni_lock b/test-data/omni_lock new file mode 100755 index 00000000..c684ca16 Binary files /dev/null and b/test-data/omni_lock differ diff --git a/src/test-data/simple_udt b/test-data/simple_udt similarity index 100% rename from src/test-data/simple_udt rename to test-data/simple_udt diff --git a/test-data/validate_signature_rsa b/test-data/validate_signature_rsa new file mode 100755 index 00000000..28ed0b31 Binary files /dev/null and b/test-data/validate_signature_rsa differ