diff --git a/Cargo.lock b/Cargo.lock index fbae2b66..3e3fd566 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -526,6 +526,27 @@ dependencies = [ "tracing", ] +[[package]] +name = "cargo-near-build" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aeeabf8f3443202828089b8b8e1561e54ad294978afcbc8a5e3095082903947a" +dependencies = [ + "bon", + "bs58 0.5.1", + "camino", + "cargo_metadata", + "colored", + "dunce", + "eyre", + "hex", + "indenter", + "pathdiff", + "rustc_version", + "sha2 0.10.9", + "tracing", +] + [[package]] name = "cargo-platform" version = "0.1.9" @@ -2131,6 +2152,7 @@ checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" dependencies = [ "autocfg", "scopeguard", + "serde", ] [[package]] @@ -2335,6 +2357,31 @@ dependencies = [ "tracing", ] +[[package]] +name = "near-chain-configs" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "398bbc9829c66d1b52a213d3094f0ec5ab61d6fd4b3c05b98aabd1b6e70bcf44" +dependencies = [ + "anyhow", + "bytesize", + "chrono", + "derive_more 2.0.1", + "near-config-utils 0.31.1", + "near-crypto 0.31.1", + "near-parameters 0.31.1", + "near-primitives 0.31.1", + "near-time 0.31.1", + "num-rational", + "parking_lot", + "serde", + "serde_json", + "sha2 0.10.9", + "smart-default", + "time", + "tracing", +] + [[package]] name = "near-config-utils" version = "0.30.3" @@ -2412,6 +2459,7 @@ dependencies = [ "near-schema-checker-lib 0.31.1", "near-stdx 0.31.1", "primitive-types", + "rand", "secp256k1 0.27.0", "serde", "serde_json", @@ -2457,9 +2505,9 @@ dependencies = [ "borsh", "lazy_static", "log", - "near-chain-configs", + "near-chain-configs 0.30.3", "near-crypto 0.30.3", - "near-jsonrpc-primitives", + "near-jsonrpc-primitives 0.30.3", "near-primitives 0.30.3", "reqwest", "serde", @@ -2467,6 +2515,25 @@ dependencies = [ "thiserror 2.0.16", ] +[[package]] +name = "near-jsonrpc-client" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d03f5dd8adf26ecf27f2a898bee6b7df80ea769ae6d7b4d6b9b49bf11d7838b" +dependencies = [ + "borsh", + "lazy_static", + "log", + "near-chain-configs 0.31.1", + "near-crypto 0.31.1", + "near-jsonrpc-primitives 0.31.1", + "near-primitives 0.31.1", + "reqwest", + "serde", + "serde_json", + "thiserror 2.0.16", +] + [[package]] name = "near-jsonrpc-primitives" version = "0.30.3" @@ -2474,7 +2541,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "963e3051ebca3bb37a4a411b44d5744efdfa0b227dbcad5e03732dd0b9672b2d" dependencies = [ "arbitrary", - "near-chain-configs", + "near-chain-configs 0.30.3", "near-crypto 0.30.3", "near-primitives 0.30.3", "near-schema-checker-lib 0.30.3", @@ -2484,6 +2551,24 @@ dependencies = [ "time", ] +[[package]] +name = "near-jsonrpc-primitives" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3feacba264550d045c5d5e26f1bdb4857a5122739590ddf6c4ed22d56d518301" +dependencies = [ + "arbitrary", + "near-chain-configs 0.31.1", + "near-crypto 0.31.1", + "near-primitives 0.31.1", + "near-schema-checker-lib 0.31.1", + "near-time 0.31.1", + "serde", + "serde_json", + "thiserror 2.0.16", + "time", +] + [[package]] name = "near-parameters" version = "0.30.3" @@ -2615,6 +2700,8 @@ dependencies = [ "num-rational", "ordered-float", "primitive-types", + "rand", + "rand_chacha", "serde", "serde_json", "serde_with", @@ -2858,7 +2945,7 @@ dependencies = [ "async-trait", "base64 0.22.1", "bs58 0.5.1", - "cargo-near-build", + "cargo-near-build 0.6.0", "chrono", "fs2", "json-patch", @@ -2867,8 +2954,8 @@ dependencies = [ "near-account-id", "near-crypto 0.30.3", "near-gas", - "near-jsonrpc-client", - "near-jsonrpc-primitives", + "near-jsonrpc-client 0.17.0", + "near-jsonrpc-primitives 0.30.3", "near-primitives 0.30.3", "near-sandbox-utils", "near-token", @@ -2885,6 +2972,42 @@ dependencies = [ "url", ] +[[package]] +name = "near-workspaces" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f02d7882561f4be4797b3483603f2ca48c9b4fde8c0487961cb70dad7c93267" +dependencies = [ + "async-trait", + "base64 0.22.1", + "bs58 0.5.1", + "cargo-near-build 0.7.2", + "chrono", + "fs2", + "json-patch", + "libc", + "near-abi-client", + "near-account-id", + "near-crypto 0.31.1", + "near-gas", + "near-jsonrpc-client 0.18.0", + "near-jsonrpc-primitives 0.31.1", + "near-primitives 0.31.1", + "near-sandbox-utils", + "near-token", + "rand", + "reqwest", + "serde", + "serde_json", + "sha2 0.10.9", + "tempfile", + "thiserror 1.0.69", + "tokio", + "tokio-retry", + "tracing", + "url", +] + [[package]] name = "near_schemafy_core" version = "0.7.0" @@ -3086,6 +3209,23 @@ dependencies = [ "zip32", ] +[[package]] +name = "orchard-verifier" +version = "0.1.0" +dependencies = [ + "getrandom 0.2.16", + "hex", + "near-gas", + "near-sdk", + "near-workspaces 0.21.0", + "orchard", + "rand", + "tokio", + "zcash_address", + "zcash_primitives", + "zcash_protocol", +] + [[package]] name = "ordered-float" version = "4.6.0" @@ -3727,7 +3867,7 @@ dependencies = [ "near-contract-standards", "near-plugins", "near-sdk", - "near-workspaces", + "near-workspaces 0.20.1", "orchard", "tokio", "zcash_address", diff --git a/contracts/orchard-verifier/Cargo.toml b/contracts/orchard-verifier/Cargo.toml new file mode 100644 index 00000000..48b7a784 --- /dev/null +++ b/contracts/orchard-verifier/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "orchard-verifier" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib", "rlib"] + +[dependencies] +near-sdk = { workspace = true, features = ["unstable"] } +hex = "0.4.3" +zcash_primitives = { version = "0.24.0", default-features = false, features = [ + "transparent-inputs", + "circuits", +] } +orchard = { version = "0.11.0", default-features = false, features = [ + "circuit", +] } +zcash_address = "0.9.0" +zcash_protocol = { version = "0.6.1" } +near-gas = "0.3.2" + +[dev-dependencies] +near-workspaces = { version = "0.21", features = ["unstable"] } +tokio = { version = "1.12.0", features = ["full"] } +rand = "0.8" + +[profile.release] +opt-level = "z" +lto = true +codegen-units = 1 +strip = true +panic = "abort" + +[target.'cfg(target_arch = "wasm32")'.dependencies] +getrandom = { version = "0.2.12", features = ["custom"] } diff --git a/contracts/orchard-verifier/src/lib.rs b/contracts/orchard-verifier/src/lib.rs new file mode 100644 index 00000000..bd0c3d67 --- /dev/null +++ b/contracts/orchard-verifier/src/lib.rs @@ -0,0 +1,162 @@ +use near_sdk::json_types::U128; +use near_sdk::{env, near, require}; +use std::io::Cursor; +use zcash_address::unified::{Container, Encoding}; + +#[derive(Default)] +#[near(contract_state)] +pub struct Contract {} + +#[near] +impl Contract { + #[init] + pub fn new() -> Self { + Self {} + } + + pub fn verify_orchard_bundle(&self, bundle_hex: String) { + env::log_str("verify_orchard_bundle: start"); + let bytes = hex::decode(bundle_hex).expect("hex"); + env::log_str(&format!( + "verify_orchard_bundle: bundle_bytes={}", + bytes.len() + )); + let mut cursor = Cursor::new(bytes); + let bundle = + zcash_primitives::transaction::components::orchard::read_v5_bundle(&mut cursor) + .expect("read bundle") + .expect("some bundle"); + + //require!(bundle.actions().len() == 1, "single action only"); + + env::log_str("verify_orchard_bundle: building VK"); + let vk = orchard::circuit::VerifyingKey::build(); + env::log_str("verify_orchard_bundle: built VK"); + let flags = *bundle.flags(); + let anchor = *bundle.anchor(); + let instances = bundle + .actions() + .iter() + .map(|a| a.to_instance(flags, anchor)) + .collect::>(); + env::log_str(&format!( + "verify_orchard_bundle: instances_len={}", + instances.len() + )); + let proof = bundle.authorization().proof(); + env::log_str("verify_orchard_bundle: calling proof.verify"); + proof.verify(&vk, &instances).expect("valid orchard proof"); + env::log_str("verify_orchard_bundle: verify done"); + } + + pub fn verify_orchard_bundle_with_policy( + &self, + bundle_hex: String, + target_addr: String, + chain: String, + expected_total_outflow: U128, + miner_fee: U128, + ) { + let bytes = hex::decode(bundle_hex).expect("hex"); + let mut cursor = std::io::Cursor::new(bytes); + let bundle = + zcash_primitives::transaction::components::orchard::read_v5_bundle(&mut cursor) + .expect("read bundle") + .expect("some bundle"); + require!(bundle.actions().len() == 1, "single action only"); + + // Recover output via OVK = 00..00 + let (note, addr, _memo) = bundle + .recover_output_with_ovk(0, &orchard::keys::OutgoingViewingKey::from([0u8; 32])) + .expect("recover with OVK"); + let orchard_amount = note.value().inner() as u128; + let recovered_raw = addr.to_raw_address_bytes(); + + // Extract Orchard receiver from UA + let (ua_net, ua) = zcash_address::unified::Address::decode(&target_addr) + .expect("Invalid Zcash address encoding"); + let expected_net = match chain.as_str() { + "ZcashMainnet" => zcash_protocol::consensus::NetworkType::Main, + _ => zcash_protocol::consensus::NetworkType::Test, + }; + require!(ua_net == expected_net, "Address network mismatch"); + let mut expected_raw: Option<[u8; 43]> = None; + for recv in ua.items_as_parsed() { + if let zcash_address::unified::Receiver::Orchard(bytes) = recv { + expected_raw = Some(*bytes); + break; + } + } + let expected_raw = expected_raw.expect("Unified address missing Orchard receiver"); + require!(recovered_raw == expected_raw, "Orchard recipient mismatch"); + + // Policy: orchard_amount + miner_fee == expected_total_outflow + require!( + orchard_amount + miner_fee.0 == expected_total_outflow.0, + "Orchard+fee totals mismatch" + ); + + // Verify proof + env::log_str("verify_orchard_bundle_with_policy: building VK"); + let vk = orchard::circuit::VerifyingKey::build(); + env::log_str("verify_orchard_bundle_with_policy: built VK"); + let flags = *bundle.flags(); + let anchor = *bundle.anchor(); + let instances = bundle + .actions() + .iter() + .map(|a| a.to_instance(flags, anchor)) + .collect::>(); + env::log_str(&format!( + "verify_orchard_bundle_with_policy: instances_len={}", + instances.len() + )); + let proof = bundle.authorization().proof(); + env::log_str("verify_orchard_bundle_with_policy: calling proof.verify"); + proof.verify(&vk, &instances).expect("valid orchard proof"); + env::log_str("verify_orchard_bundle_with_policy: verify done"); + } + + /// Build the Orchard VerifyingKey only (no proof verification). Useful for + /// measuring gas cost of VK construction alone. + pub fn build_vk_only(&self) { + env::log_str("build_vk_only: start"); + let _vk = orchard::circuit::VerifyingKey::build(); + env::log_str("build_vk_only: built VK"); + // Drop immediately; we're just measuring build cost. + } + + /// Parse bundle and build VK, derive instances, but do not call verify. + pub fn parse_and_build_only(&self, bundle_hex: String) { + env::log_str("parse_and_build_only: start"); + let bytes = hex::decode(bundle_hex).expect("hex"); + env::log_str(&format!( + "parse_and_build_only: bundle_bytes={}", + bytes.len() + )); + let mut cursor = Cursor::new(bytes); + let bundle = + zcash_primitives::transaction::components::orchard::read_v5_bundle(&mut cursor) + .expect("read bundle") + .expect("some bundle"); + env::log_str(&format!( + "parse_and_build_only: actions_len={}", + bundle.actions().len() + )); + env::log_str("parse_and_build_only: building VK"); + let vk = orchard::circuit::VerifyingKey::build(); + env::log_str("parse_and_build_only: built VK"); + let flags = *bundle.flags(); + let anchor = *bundle.anchor(); + let instances = bundle + .actions() + .iter() + .map(|a| a.to_instance(flags, anchor)) + .collect::>(); + env::log_str(&format!( + "parse_and_build_only: instances_len={}", + instances.len() + )); + let _ = vk; // keep for parity + } +} diff --git a/contracts/orchard-verifier/tests/bundle_hex.txt b/contracts/orchard-verifier/tests/bundle_hex.txt new file mode 100644 index 00000000..9d3fe0ee --- /dev/null +++ b/contracts/orchard-verifier/tests/bundle_hex.txt @@ -0,0 +1 @@ +023297c779c56e8eae0c6f0273a6a297fd6f0ca80e35a20925af92c3f41e2e1c2dbd2b391934d360260b6899cbb7ec693cda5e9302e302cf52e9db45758b147b24666abe77ebd51776949c541fbfd8c751fa86ca46a2cc8a90dc139d8688da11094dc33ea7f36e7bab9ee8f4ccb026325e6613f9ee393c9ec1a6a675d9b347241facf0dbfe976038cfb3421178c8ba80181d9125a1ac48078ec35cdbc6c8a46a3db39163a32cdab5feef5fd1d44f5c01811675b29bc275912afe9a54469fb4a8c01378b94fef07cf2d64560ba363a2bd204d4696ad648dfcd257b54bc78895460d2d38d9c92f23bc79127af8854f0b76b2394e0b16b6bab2209b1160be40fbb590b042bf4d2794fde0317798cb4beff141952a583f02b80a5beed954bf1c2fd5f452dfe1112fa7eb1b7306edaa24622197359afcf914d01aedd5607dd7394cd6042f807d98a5d882f8107ea32f7d6ec142273872f07b5b40ddc1eb6af2f5110894141d07d68f46f19de980563db1d7c6f929ea47b5eac3d72121941ce0a7a82bc5edb09ff824d3ab44321402e7332ac166c9aa9881bded46876b13f97a7ac3ff9fc7b3dcdfdfa9a01e69866c05a89467a2e35aaeaca9794c7df88041010735070887033d969bf3207048a173b2baf5ebca9b4ea2e50313f9da97d18bcf063ca10218c3fb0a4825b88d0c03732d1edecd1deef8a91e89442d99cffa70c2b886c37b0c0f451b0cfdf83fd3b996961439235d91eace6b72d8cf77ddeafb37e4d5d0f4f3fc53009330da4d9d0ce562499795f7ceb0916ee9430bbf3cb86988a376bb7b14c8cb5c26458c5dc63f595e60f8890ee68b548df6fcedd51da21d8e4d43becc337dc0ce46590aacdde0869b1caefa224838eb20867d3c6e99478507a45a7fbc36b76cdc07433bbcca3dea1a3182a6c160b85151f792fbb53db3f47e775ebbb9b59b6fac720a5a5a2654d2afe1004d2a270caf7f0f128db1395cb85b981d220198b5527c458718d63ed5a6f311f6ad34a2bd16462f63706934ecb1d4ccebe1281487d492c00050ad091f267e2b4744f8d8a6d80c0d38a0d9765616fdc4dc24d4acdbf79b103f8fe582bbc58e3c939eccadb5d62aaf53b848be3ddcbe3aba455b0a4bb9b1a854a06a3c6898435ec922286e6d4e90e5bb7f11594919aab7b20930748cb66295942cda2a32007c2982187ec60140251a9058aa6ff87945a5619a18ff557a7a67981c5b9a85133c9dee18c9ac2ad92e0d661ea58ecd209691d3fbebb5987eb2b8c7b00fdd11b771228602864839b68af3279f394021a150e0e919839239335080a87bbfd0e593ad41f124f82e6c2b297d33531c81634336287d4e18ae37ad12391f2e44fae88d77db2e621f67cf72abc8701fe8af3d5c7fd953ccdae2e702ff3bc2dc21d07e9c932b32aafd3aa1fdd2c15f5758d557361e443a85f4c8ec0bf9885165ca6607306ab304560b7fcba8f04ad355ffd3a0eb041b5ef7852272de5b7fd133f342b86ce957621316e170b8a71d7a75e3cb8c152fdababb5f78c8d0a602b5ca5959260c52f18eba5e33abeee0e0a776d6c25e623001572fc07497f499f97af241a8056e19cf84005f28e881913fea16d6fa2d62863c975948afcde3bd7e3381b10969aa71c64b7f8a9ec68007f2a204fd6b24c62f19ba3c43f945e2bb3ad42feb017e98b2a4afac6fdb24438861f5cab5c67aa5080769236ece171aca76cf035d627ab324845613edace3c7ddfccb54a21fb85c6dbcb90945f27f1831b44553f4f1af113cff5b1a1e5c52b8738cc8f1e5aab690844b7fbd6e55593b6cb5859e13116c748182dfa35a5cea585a540be1242547173e6a22af6bfaac6184ccbb1cadb3c2449928aa91cb2248b3d4901ad96f5b16fca2822b712c00b102f68e735f798737d7bd34d987867d61366b47743b55dd2d8014cdce0e5ece071a8107808b522cd0cd1880bd5d1cce457445076899f518176cb2970c4aca68b43f2fe8bb183ab4130af48b97b4d3cde192a6595ef5b262ea449b0bce7e2677f93be4b023aaa71b0087e1c28c3ad6aa6fd3870834e0b0235bc75c220f492cf98917f82105b7becca93e1715538cba35e855311d4a012c42d437aa74da38364baafa7fbf2bcb5f0edbe257653c578bed9f698c1e3cf3b2dbd81f91946b2db49835a48aa741e82ec959795a0c0d7cdeac0deac914e71b4fcff44f2dc9f7fe50f8ab801ea2123ae60a792da01211a9ca9ed81e2652bcf7962826b2d86822bbdaf67cbdfe6c47cf21bf814683c6b71b5227689e8607a7da0167a8f8b6cdcb0730a8a7b7e551553fe003b03cffffffffffffae2935f1dfd8a24aed7c70df7de3a668eb7a49b1319880dde2bbd9031ae5d82ffd601c0cd0344b0cc147b4c0170e1c7d2b73ba2bfcc2b977d7861f9ebefd0a96d6afa950c978775ad24880019b71c60d32baa70237077aaefc321a7dc529a9af719a8f1e5866119c65cb24841345fb92256fef509db7554aa07a297ca643ea8a5689bd6c2f5a108d06e503a77112280a2127b6758082d7e5bf0abe8c385f9807092c3703704d1c4d33cbdf120595b7921530da4e4d0f7652491855eddb528a362c499d8fb8952678e651ef15a1106130d1c24f352742075cd5be8b29fedfbdd6a900919d73e02693a255c4ff2caa303e7d511864b2bac44021d9bc6059b176b15bbf0471ad03f79f1b39637eec9956a391f97c2dc3ed1b8a9a4cdab20bfa01f48cd1b170d13a2d99cf1a841665edcf88878eb5e01abe43846780d42fe681dc0f54ad323db6babac9c0ac890b370b088870aec4c928fe95cf9e45e297415abb93c8adbd5dc49f444430ecb069b9d1e71099fdd07bbd98657bb748294099d48609c7b9bf37ae1c9f2c0415b7f861ed32dd2d06e7059cb9a62a769f0756bf59d690795b29f7cab1c4b22553aeb1313f84653656a2e6b8ca97431acd7c8f0374f5422feaad29ead60193e6df667d2261cc06d2f1e404f33c19b0ff858bfa662a7af266812c69c27fc6ecb12902fc05eedab5077cda1422a0b30fdbde4b7b83110042ee6e99264a30ab0d208499e51b9e8c36154af72d1ec9b57c5ad55d2d55856d742e6714014fac17fe3cd5330feb8f14dd6b77c89f993089827386358bebaa6be7ad3ea06138e508f433817da05d2ad718ba495e8ac51c99479e2ab0ee2cf9489ea7479eb863fb315f6f4eebeb4a2a262d759ddbe366c0e80c2587e12d064cb0b94d8389e032b4e84151a73e5e836fa59bc44da9e71df8936c12aa00e37ec6731d15120e09e6bde669272e65e42f847a3e57820518dce34c3a1919955bc01453401935ab629b299aabb5e6d36edbaa75f290c3ff4e7232f0e62a01e408e0ad59994cfe9fc02a5361f4fe753d39ab30ca8d445d5d1b3ea8af79f6ef76e4d6898774abc28626321c47cd56500b4d859e5844e32af4a3dcb3ccc6e4597478ee5e426a7e90871ef8402efecf0a0763e1e44f7d12e1adc559b19a6cbb7c8374d64fdc1038be31ef73d644ab16f53a089ffd96289aa9d9ab1595130c9518a2ee4153ec4296741aa8865369c50923e543b8a822ef13ec601bcce2c804add223de8d48a8a7ffd734a2a86b55b4c3e810e4bfa743504d8a184d17803c852db3e38606d8b2928774ac103a72e0f567e23ac304d49e80a60a42f2137c15557cdd58581ed77e29c25c20a679f01c16f7fd6504ef6755da4392771c37497cc9bb4f32a77716ff2d2fa4a1630cfc42a5688079d8e6ae9604d68176ebd062b6e2ee07104b40bb9238f5c70952c6a4f1971e4422c7ea945433d8ca98edfddbe0c10be7dd37462f0df355ac3ba76a866e07b2266ff8d09f72250df6ed8620cbe7f05d3eec700e616aa0cf1d0e137dab765bbe9d5c26a1bddb2d47dd5e56f7673dc5c2ac9ac8c3d8a800610d25b6cde75a1227a01861eda843e7640330504edb9db5b0eca7e331811a6998031d6c977ee5a9cea8855c86f37e6addc7249ca6bad5e3bccd4a5c2eea6aeae5debd336ea3aa3cec37773e96c4c3a507ed544ddeb7586bd39d43986de737ff2019088a1ab96d2b37b4e30f5e1d495dd3632a52cd06d78b41647eb91bad5915006c0794cd3c0fa18d2f41ea586394d0c1f9c9210fce4408f41bc394b2eef5f7a4651ef0009c3b9aeb1f86bbd0183ea5ff51a66accca9798e0de500d40057e206288bdeed1e07fcad6650996e48e448d38ac6f0c751a070537d46810648ad9ac8e22205703b74dabbfa929b2676327ca1a868b914a5a3050b1bc55ec5e5b958ea8f629f9d9300a639d5e791b8443d3ede87c7de66400edc77ce90793f5f4684d840c2d09d4caeea38129480fc4ac36073f850fb23a1f0bba789a268de30116a48b43355dbe167fee48a69a66e74a124172b9d19d62c8c5e589d7e41026ec945f35fd286c7bcc4d6b26c8ed07cefb9126478428cc3672ba0d38f94acb6863dab9822b008b1750b69a5d9446d89c0f1c0dcca7bebc910a72a911951a3cb53f714d7f0c94229e5ff777c158f614a90249b3da9851eff750a2da18b76efc2df090f4110e2cafa95c5a65f333e98be7520f49288d9801c2923a4c5f5a18c5168c5747a7a9be5b315ed022cff2f249a2db6765f26f8975818e75046c4a0933da0d33e752eb8c34b0f60775c9066b45644d731146bcca10974f8d3a863b2eee2c3403859fd69f4505c7782682190fba791d76271d71ba44a48fd7ae49f496acaa2cfa3979f29912b9622e0c00c9e9b0f28770ec9625e9ba07b32f4f50778b3548eefc9d9808026e13f15f4ec541f922328f8f4630fe0f7b55f7d26884e91ab2e5bca87c64ec2b954667cd3a21e217dea864318a9d444f4cd756d5eef9fc3a922e469368469e0f769e774082bb4bfa4792d333a6ba3fd46de62717babffaafdbd884147eb59502698aa2222bd96d391184cca50c5257d87e6ace7177da4384af3dd53a88b1903d90be3e8c85f92d231dd5037d83ce0509985d28ea059e26ddbb4c2af5c77c801713f19101231307843706e2b4efade83a61eebf5ab0a9a0453801c3d0d55900392d00081bbabf6bcc38dfe4b8002bc09cab4d8e8deefb6316f2d26a7d8dea2e0fbd4d44f665f08dac6c05ab04f59079bc778a7c7ab3321703ae1747580b2135193f95fd2cad9691e1a2e1f652d43fe9ee6f45cd24b719e1f8dd1b4c7d91425d01aa87b1e38bc319c87001ccab8a75fa4a813731fab0ef858c7e7baad82110a332c68531210494f4bda964ce3af9aa38470d086e495361e0846a90cdf5cf725a05d661c42aa452d0337b0b7b056c552c34028dee107e895caf50f66e2ffa93740b91e60ebe28b456a3b0b278deda6a4d866067524ba43aaa4d1e6a62051a488f1b974603431761f01620d73a81638a706434ab1a803eb66bf4b7005e566f56681c8e23a57d687617ad9752106c93df1eba4c6c3500d74675b217a2eb009fa71f075d9ca76f6414fa1770c183eeb8091887f5ffab8ecab3ce50bea4541ba9c02a2e4745452859b9f009818db266f87d0eb7fa93316179b0283591e260d0386b8f25d93c2f5b804843133ce0a89ae5ad0219d3203d25469e6eca34b3ad080aa6e6340898a818268be37b704d5f80677ef75ed5f493fa4054fca98bee7a2cae41bd1f12e914b353867e7dbfeb36ecd8e54bbce0a021207b3a82cd9b83637f18dcad027de934156224bdcc604e7f438a983cc1d0a67eeb95ce71c09cb46314d75e491b8c9f62b46f53036d40cbc26f39641c54b7b1a2ccc1685d0ce255ee681114a2374e02869c5b7c2f4a546b3b745b9119951d610bcc0d18dff4fdf84456bfd9f00056e9f69d1fa4a0d9f37ae6f1629922af81650cc8cdfd94f9773fe4ddbf400b10c3c0021feb906276e57e7cdf581ef725962b41f484e832c28216268d2ce7fd39fcd462cdf3242c34cea6f7f6a60b0634ddc602889ba5e6d2d06369000e6857029b5f7a29b6d84916abbf4c73ef4d938a0d0fc989e569544b6ee6ba4a94c6d301da0df9f046e12ac171a345df97a602ac60d4bc61ec0a3c3cde88ab959777390c32ac7b86f5c730f43dd0646b7c8e99e329ada2b56258f336222126002a5c5a38728d3b7e1de112358f35846bdd67743f2971ca43ae6c223f0886cea3e2ded93433e7a2dffdbe1c7994d11d030a68deeab5db45b1b5ff6e6b4c215d956dd13f0186079c7234b305e401d0a03bcfde39e4518a4e1a03d2664b726c0c2b1c549e345164033b4772cd57a44c1788ac43d7afe6a6736a1e53eae7697e22f652ed2e3dd07b736269ab16ba85ec734c7ddf2e8e57a3fc54187a3d84e52e5c5fb8d7db19b4cbf29a1744c7a587bce335561b0886ac2caf18e310920ab36020418c411f2b8da7937e0a5bfe19018406285e8cac03930872ce80360470c3e748b235a3062cea6f62c8591dc84b10c074af1ce49b51a55ac57e0496d0fec725c72ff314a8097eea8b23ce44e0e5c2ca39c2a5ac0856429b6f7982a4b42a2409b82bed83941642606b2372da58409dc8c55af279766740cf6711b474c95d8b87335c9881d816681d1fe453265ae36de388718d01b7f7947790fd2de5ac5a40a5ee32cbed6b1625c72f4d8f296e2b7e1376bd84c1e67372ce1e84ce9ebd76e4bf1c5d24b6f43a6db6506138bedb95841f294c01bb71511d91f39545f3f9402c86d95921723d15386e20922b4090619e73d55592818a3d6c49c21fadc53b8015e5e9ab6a821a1ac1d7b67056f1ff3dee8f603e5b27149a47ed17333a7dc601f50aa16fd4b5f5294f2c724662b2f4cf0a3ce6fd6ed982c71aafc47a1908a2bb9a9eae23b6f6021e5e898774872a46f651267bf2e251dacd8b5edd4b267e263519d6e6e675dc83177d7b9876f0eb98fd8c84653b2901957259bb40ec843b4ee9ccf6542fe124dc024f407fe6b02af3fb7b3017df5804c396cff7cb0efc54ab3a17bbdbed39233023f8682ff1545561b1df0c22ad04564997d00ba758e732fb4ba0f6f4c7588f38233e3982dc1dfcb6e47bde8f46da93f650fe2bd259152d7219c6585645d19715186064390b4597acebe4d6b283af4553b0b6cdb550052dbd8a710833de7ea7902a8a71e3bd59895360bcccdcfd7a101adeadf03d9bfe1d7bcc3eca21f88156ba2febdeda6741f01fa9bd07949fd1e8e4ee6631dfb5c24d307f83b62a2bd801363eee3ae4cc8df449a426971e167bfb9ed1705fb1d278c3f837360ad0af488b983c04dd41fb97bda5d81edc1f4f655382ea1d11e08784c30e62e655608128647314614e25710e0d99e773f3769138b0f2ac80c8105092cf4d4e3eb4d3c193fbdf2ff5788415168c6843ec739d358fef938314c4035cd758cbdb5f2429bf357af215ffc64ce76bdd0d0f599f4ba5c016da8e23ed193b92eb47397805f74f7dbe47248fe57c3a8bd1cacdbf634e3775865e48417b4b08716c75f26c4afea844fb4b39496794c0d7425e35c4ca8d37e47374c33cbed4a81b85750e6b6047ba8019af04c96e2a3f1f7f920df5661058871a7990ae5611fb6f79bdc5853db467d212cc392cf9245b48377dc39500e4cc4ce56979b8fbc924a4c54fa4975e2dc8a0e2b6190bb664c143e0f7e7fdbbf02cada9d30b6ef5fb521fdbc65b7115e016cfbcb60e3f2fb953fe2047c83bcc9f980c002e106e93772359f2464f58023edd5c92d511a62777c2604d2f85e726cbcd1280403b7a22392b71de9e905ddb98597d6b592b6bd6dab49027f930f494497248e1fd23de4613c4c7f21917c3723e7ee741e827849d7c5e43ed6765d76795b956dc43d069adae9bc87508f1e5522095c1e1c026c78cdcd14f60a04bae682dfc20179aee20a191bc2d201debec030a3ea1e3d41b0141be623ca4909c5d0b2ae1fa7b13f4209c02c8d9405a3b12c5530672302a3f176311ff1d3f1b5a637c57b8c16ece9416649696c68d0e31ec4a6a3682e7372c1b46bc88f783f9cd0b46e7148379d795cffc8a7bd825a3575903a6872bb3261137751dd3ff923b2f996229a0dfca7dbbb15703022d3d2abc75b99a2731b04c1f8db968ea6251a6755e4f65b42729827037ad42a7d6fb5da4550e1eb9b4b5e53ac9066016fff9e0d488fe7ffa0d9373f8825ff6bc5123f62b66ea2c1d365551299ff45034d0e0763ce370aa9b2894e2e2d8d349c665b846593d7a8f15ab739d0030183689cf4c3aac4869f9c5861d72b8b01bc8fd361f1baea89dc131d00ef02b96440c253cdf02cff2143ec2b91b5cb8b11b55303cfad695a11990e0516f0724a1891ebe190a3169a2f5466f17016efe56943ee8e3220c748e0096a9f4c7302cdb9e577670d2eb9c9bc9547265a5da5dbc8bab69cad5715951cbacdde667b92e975652084b0fbb291769d8869cf846e0c353fec20b0095b69da543df2e8f1b3257bea67aa0ad4357f0d8bc3ffaae7f76e8e607d6bf505784b3d7966ea64bc80ffb7b86f2845277c42b405deb4f1f21f69985f203e755769bb6883e3e4442060fa76e9f17c044fa1a0f05489c57ffedadf4b140e37b419a59fcd4f1412cb93201a8c5069289b4b65ada5d0d220874443a519c199fd1f3c868f1c5e40c13410c275c870fcb919a22d76d7854d905b487828957460b6b4ff64f2c13604ceec59b10db50a1cbc902f23fc702d6ce7d2ab0347274e93c400ef7e0ddb8e856ac57a73f4383e6009e176f66ec8402e02d3e2856953421f29828e96d23daab3633d3c212e71a63b5699a244704aef71a7035186b3ab2b8debf86cabeb13b164a4b850d22dd443ee70d4796bcaeb66a2e5845d6f604b9bc2b5a44b253ceed1f6af01aa51d67ce01f6886983fb8be858ad919a7b4e49330dc30dd67560c0782140c294b93d09a60a3e508dc9189ea4b7439c074d1152b0c06cf153844bee5b498cf474c9370ec326170d1f64558f0e65f0d4c1360661342eea8a414bd0612ff77aee72ac12f53266be2ab774af54d344c9977b778779043be99c5352ba9bdbbb3df1b4080361ace1731b75ef378b1cb6d9c4d61f71e7c122e20c511af89fbd58dc49277f2b6e06a4b466c9a15da167254db7406f5fd90efba39a39b3a0c59131f1a2e61e0e8f128b5ea5b9128eb6fc561cf56ee69fd4e95c427f70d14054821d801cd98e0142b4da21d47ab4815f0e3fa3ba3f5c8e1041c3376ebab9d918ef6ee9e0b5f815da2dc9c5de4cc896f4cc19538f5c04bb280e75c93417722f5d8684b2f1c7223a706e37b2a6e806853dd9cf2ed94de10c6fd192e6744661f5a348eb917589602f3472e6fa906076a964f27724c19f67e49bc128ff06bc00726a204caaf0ce47131c8ca8ae31a26b035d04a46ac2bf865815885ed275cf8fd211d6863f51738c3b986fcd60661d4d7aaab84af40abf4689888c56114f685d575c4c19f6a6bf0f31359049e5f10bb239b906acb20bfc2e19126d1eee46531224012700204840c2310c14179d31e2836ade555555373f097e19ef4c324c72390e3671590ed6b4b921cc7bb7dc78decdb848f1d6fd2bfac599631e0106676e47271312ec1e8bff9e278251c6f22760dea8dd66dd9a241b74c9a5e885e250591e8c508719ebee69b527ef5d07d0748a04e92a6b44955e24449766711f3578c3393067a6c6c7176cf912be66de2cb14bb95f2a7e8d9b5652aa193abbe063e07dfdeb67c1457c13d0530750fa40d75b84ff4d7a30a6e24b4f46091184a53fee19bacb6b5118faa6bcc508fae19d7dd4f221fce79f765ff3eb13989d9da1e1009976c3b4a7e06a5da7b82ef7dcff1bcf3a99bdfb6c1b3d27dcf8493e2cb895d1afa54d4afa2fe2164703370da8784e784f3f434afb4dd1fcef340a61cf89dfb7ecfdf091825b01d25bdf025f1117c17ede6f784cefecc6c2fa07f4b488965fad9f66613e7fd24d80139b3581ba2dd98e9c4b1d0e0257bd72c7565ebbad6e41d6f2a20f0f7ef2c30ffca20a90a6458c644333a6828c4d2b6498ee588a0e03ee5bccc483281e929e0af76400053a8ad5822aa9c8c660f7a396e3cdbf88e1269e8814c0d89eb2538a7e9e001e7e5314f9e1408694fad34d77ab07b847b3c5c74fe916405cad0503d23fd33d3dcac5803dea7f7d7184c6e532abac23ba9a8218109d4a68def740e22428ad7d37841fc5d948281702f881658694206c0e7143bd07b75b4f3509de38aceb4beb0fd2dd44c6d4bc5b0fe813a00a33cf13874ebfd371714401bd558a3eb0c22fe617e7adda7c14a89b379fb3dd1180cb79d8e9a6e1622ceef7534239f9849d8a3328a2084938c3f69679cea5247e685baa5899610e27096f41ce58946f2daabe1a1b4042b6b633655a624d40fa85b629e83214ef240efdecfaaad1ad916d6ccab53a76739067b67475641206540f57f03f5de652314f9e7afa640f0bdb881bf915211f8b87e62f45c2ae48e72d48581ce5eb69a51984bf6c098069f92f680cf1a73989f2e4c9cc264e18b122e697024a45e121d7e38b1fb693e24d68fb039a722d192b97ab2faa38c4108150b6465d6141fcf365ab88805577f943cf4847524114133bcfa5601e76526e6fda0db7eda46eeb5bc83acab348bb41e028c4fce2656e0c7525dc9bea36f31dd9ebc3e1e80ccb56fa9a1af7318d626c98595776b2874c03c6befe3aa4b5444c4a06fa20c2758967d344e483a9df5868bcfd41f2c3a61c2907734ed05890b4c749a8064296388e2cb4a1cb6411aedd4f8ec7b598980af011f9401a05d5500f397bd271a90790fdb07fc12ea376259f25840c9dfcb1e38618473d997924425dff80ba3fd3a0481ffe34ba78f7ccb21c5040f471005bbf471d0e5764ad93add14f4d9ba269b8a989b83f09bdee503b09f7f981cb799d1c8319d46519913d1d1666f7ca7c09a917464a33b2e718fb5d4f5600a6009e29ede43dbbcbe03bd8e55ec5555dabee5c8a8f392a23558582ed15a883de1789fb769e31f89ab1ebb9ce5dac7c3bc7927f8a154c515cf35988bf822a3a666ac059d35613b96d7c15e0abb8d24c7a17fe1201dba03cc17348b5a312b3cce0d54c15456117042e489261898464dda8ed6af0088be53b18b9e3306a3ce8bb61a83b95f9061378fd209a6b75e4d49985fc76818eb6580529a23fddefbd1da43d86e35b7a4c0653e1cd4fc544e7c50b742ebde248734628bd3544346776a497618173065e451610037a92fd76f3abd1a82fcc22d8ff274fe048040071f07dcf268ac540b589234d94fdad7a507111ecaf92514fa1460604fffdf9cd5a1aa3dcf0b05eecb3ba0962fda738f481e4f2fac9d62bd328b5906f58a123d0fa586efa881d2b9d48e0ae530204cbbec5ddc9fc1c0d0ae0f482e471553459a4827e0ad3e82b43ceb5012fc1d4216a1b583faa59f193873a3a1517edf120ba261a0efc2eebd2da7ccf43239950d647e56aae29935697e9784ae9c3b3a121a47c7a48ecbf993c74ee53a321df279a73c5860bef10ebf6791d2c78d42ceb5c9dd8655dbd1420658e3e708e358596b4425014830cd80b7ae5895e91b32bbd619d0793d8cea8aa54cf97bc0b153a422abe40f31f23be38b6461995195734604fc93c7a1b78c4cb4b8d3d5af80401d740d6d5f2c4e241fdeb8269e7d44dd9e52728b0e79254ac09a503c526a8b89b1fb950db24493ca051889a5b8d95771593b0d1a11f17bbe1f06079d4776038d8773b816426660eec996c297009fd8a3e1ebff5ebba0207c8ebb402bfc05284516f09091999e5805fabc3294d054eb4fb2e97339756857704af2d837d1e4ab36cf3373b818e59ccd13265df9a6cee9193cda1810962d4aea5e0614ee71fb69781dbba4490dc975c0ca2b9a41143e3cb499d670552da8799eaa816ba27edd03be2d98061bbe763cfda8ec15a98df55ed8f7224a4522c9ba73c36629136f7f2aedb44affec40cbbca1627fdd16c26c38a78f38a6dea38d1e9371b00e77d48c095b13dbd69f801f0fad3de4e67e210abf118844fd39c9fbe5607daf8433d02662610a7de9ba9028c15283d4a3f9424982f602d9d53a25f28ddc16ce4de7b5b6b817429fe9eaf95788e50c59f1a0d97c5325e60bf9e95bd5a9c7063acf06cad7694eb41d71bc9109909e93c848f07bf0e0da793a1215623890dc236da4cfcb86f90d2d8277aaab939311d7119d3a8051edfffea8b79d5af1d312db63cf64e38d99e232e85b4ddf0d19b84e8d994d10c61fe8ba18130794daa8d1e2cf131338b80923a652068c5726a151033362ede8f3c1dc7d62f25fb680a1e33e3fcfb7a50c52113d58a4d268d73dabce8c2a7c5f7040a34ebf60d8ad7e02b572e9e4a9d2f0708c2f03edd9c67875a3d07f08a32436c8258a32998eb0d1e6aa9f3ce543b6a661716b2ea029f61ec465bbe69aae71d13de14c7efa8aebff90350c65401d2926008df3859a98df0e21ffcfd30ed2b9f225ed8ab23ee64c182d8d445e968277154983ca447643ee81ddff978dd60625786a93a972879ea604b03f664010af4eda414ba02984571561f79753620aec1a44473caafcb7fea3c3c1be154bb46490b5a179f280874b413d234e4c6cf526c4565b3b0f8fba3c6efbcfec3d14c88ccc8a92ce2c66979bcf6407c3257b616fbb9c0eb837d40c1dc17acab31c7db30ac29b20f043a833c79acb2bea160d58c122413d3e052d3cd46be92ad81e179b827e304331a29a1e0e2c4db8cbb0486f9596aebefd0a089869a2271ae8819e439c61de822be198a771a3d749387b3adf82b03ca53a52aa2639caf141e607cf962ad5cea23deb6b0c22958df214b139bf779a9a50c6b93fedccad8ead1058500ca4dbebc16acebc8ee5d24c54e924c7c428392c2806d904bc7cdaab8f6f45c380691d8aa23027297a8d27973e1082ccc43db80384b57c4b53ec2857e2591843adf0ecdcd855133ebd717bea32d5bf89ad916059e7e97cde2a039dec803a9c78b8433beff03 \ No newline at end of file diff --git a/contracts/orchard-verifier/tests/gas_parse_build.rs b/contracts/orchard-verifier/tests/gas_parse_build.rs new file mode 100644 index 00000000..f515af81 --- /dev/null +++ b/contracts/orchard-verifier/tests/gas_parse_build.rs @@ -0,0 +1,145 @@ +use near_gas::NearGas; +use near_sdk::env::account_balance; +use near_sdk::{serde_json, NearToken}; +use near_workspaces::network::Sandbox; +use near_workspaces::Worker; +use std::fs; +use std::path::Path; +use std::time::Duration; + +fn gen_bundle_hex(amount: u64) -> String { + use orchard::builder::{Builder, BundleType}; + use orchard::keys::{FullViewingKey, OutgoingViewingKey, Scope, SpendingKey}; + use orchard::tree::Anchor; + use orchard::value::NoteValue; + use rand::rngs::OsRng; + use zcash_primitives::transaction::components::orchard::write_v5_bundle; + + let mut rng = OsRng; + let sk = SpendingKey::from_bytes([7u8; 32]).unwrap(); + let fvk = FullViewingKey::from(&sk); + let recipient = fvk.address_at(0u32, Scope::External); + + let mut builder = Builder::new(BundleType::DEFAULT, Anchor::empty_tree()); + builder + .add_output( + Some(OutgoingViewingKey::from([0u8; 32])), + recipient, + NoteValue::from_raw(amount), + [0u8; 512], + ) + .unwrap(); + + let (unauth, _) = builder + .build::(&mut rng) + .unwrap() + .unwrap(); + let pk = orchard::circuit::ProvingKey::build(); + let authorized = unauth + .create_proof(&pk, &mut rng) + .unwrap() + .prepare(&mut rng, [0u8; 32]) + .finalize() + .unwrap(); + let mut bytes = vec![]; + write_v5_bundle(Some(&authorized), &mut bytes).unwrap(); + hex::encode(bytes) +} + +#[tokio::test] +async fn gas_parse_build() { + println!("Starting worker"); + let worker: Worker = near_workspaces::sandbox().await.unwrap(); + + println!("Compiling contract"); + let wasm = near_workspaces::compile_project(env!("CARGO_MANIFEST_DIR")) + .await + .expect("compile orchard verifier"); + + println!("Deploying contract"); + let contract = worker.dev_deploy(&wasm).await.unwrap(); + + println!("Transferring NEAR to contract"); + let root_account = worker.root_account().unwrap(); + let _result = root_account + .transfer_near(contract.as_account().id(), NearToken::from_near(1_000_000)) + .await + .unwrap(); + + let contract_account_balance = contract.as_account().view_account().await.unwrap().balance; + println!( + "Contract account balance after deploy: {} NEAR", + contract_account_balance.as_near() + ); + + println!("Initializing contract"); + contract + .call("new") + .args_json(serde_json::json!({})) + .transact() + .await + .unwrap() + .unwrap(); + + println!("Generating bundle hex"); + let bundle_hex_path = Path::new(env!("CARGO_MANIFEST_DIR")) + .join("tests") + .join("bundle_hex.txt"); + let bundle_hex = if bundle_hex_path.exists() { + println!("Loading bundle hex from file"); + std::fs::read_to_string(&bundle_hex_path) + .unwrap() + .trim() + .to_string() + } else { + println!("Generating new bundle hex"); + let hex = gen_bundle_hex(50_000); + std::fs::write(&bundle_hex_path, &hex).unwrap(); + hex + }; + + println!("Calling parse_and_build_only"); + let tx_status = contract + .call("parse_and_build_only") + .args_json(serde_json::json!({ "bundle_hex": bundle_hex })) + .gas(NearGas::from_tgas(300000)) + .transact_async() + .await + .unwrap(); + + // Manual polling loop with custom logic (e.g., timeout, backoff, logging) + let mut attempts = 0; + const MAX_ATTEMPTS: usize = 1000000; // Adjust for timeout (e.g., 100 * 300ms = 30s) + const POLL_INTERVAL: Duration = Duration::from_secs(30); + let _result = loop { + attempts += 1; + if attempts > MAX_ATTEMPTS { + panic!("Transaction did not complete within the expected time"); + } + + match tx_status.status().await.unwrap() { + std::task::Poll::Ready(result) => { + // Transaction completed + println!("Transaction finalized: {:#?}", result); + println!( + "parse_and_build_only total_gas_burnt: {} success={} failures={:#?}", + result.total_gas_burnt, + result.is_success(), + result.receipt_failures() + ); + break; + } + std::task::Poll::Pending => { + // Still pending, wait and retry + println!("Transaction pending, attempt {}", attempts); + println!( + "Time taken so far: {} seconds", + attempts as u64 * POLL_INTERVAL.as_secs() + ); + tokio::time::sleep(POLL_INTERVAL).await; + } + } + }; + + // Fetch the txhash continuously until the transaction is complete +} diff --git a/contracts/orchard-verifier/tests/gas_verify.rs b/contracts/orchard-verifier/tests/gas_verify.rs new file mode 100644 index 00000000..41f5633c --- /dev/null +++ b/contracts/orchard-verifier/tests/gas_verify.rs @@ -0,0 +1,140 @@ +use near_gas::NearGas; +use near_sdk::{serde_json, NearToken}; +use near_workspaces::network::Sandbox; +use near_workspaces::Worker; +use std::fs; +use std::path::Path; +use std::time::Duration; + +fn gen_bundle_hex(amount: u64) -> String { + use orchard::builder::{Builder, BundleType}; + use orchard::keys::{FullViewingKey, OutgoingViewingKey, Scope, SpendingKey}; + use orchard::tree::Anchor; + use orchard::value::NoteValue; + use rand::rngs::OsRng; + use zcash_primitives::transaction::components::orchard::write_v5_bundle; + + let mut rng = OsRng; + let sk = SpendingKey::from_bytes([7u8; 32]).unwrap(); + let fvk = FullViewingKey::from(&sk); + let recipient = fvk.address_at(0u32, Scope::External); + + let mut builder = Builder::new(BundleType::DEFAULT, Anchor::empty_tree()); + builder + .add_output( + Some(OutgoingViewingKey::from([0u8; 32])), + recipient, + NoteValue::from_raw(amount), + [0u8; 512], + ) + .unwrap(); + + let (unauth, _) = builder + .build::(&mut rng) + .unwrap() + .unwrap(); + let pk = orchard::circuit::ProvingKey::build(); + let authorized = unauth + .create_proof(&pk, &mut rng) + .unwrap() + .prepare(&mut rng, [0u8; 32]) + .finalize() + .unwrap(); + let mut bytes = vec![]; + write_v5_bundle(Some(&authorized), &mut bytes).unwrap(); + hex::encode(bytes) +} + +#[tokio::test] +async fn gas_verify() { + println!("Starting worker"); + let worker: Worker = near_workspaces::sandbox().await.unwrap(); + println!("Compiling contract"); + let wasm = near_workspaces::compile_project(env!("CARGO_MANIFEST_DIR")) + .await + .expect("compile orchard verifier"); + println!("Deploying contract"); + let contract = worker.dev_deploy(&wasm).await.unwrap(); + + println!("Transferring NEAR to contract"); + let root_account = worker.root_account().unwrap(); + let _result = root_account + .transfer_near(contract.as_account().id(), NearToken::from_near(1_000_000)) + .await + .unwrap(); + + let contract_account_balance = contract.as_account().view_account().await.unwrap().balance; + println!( + "Contract account balance after deploy: {} NEAR", + contract_account_balance.as_near() + ); + + println!("Initializing contract"); + contract + .call("new") + .args_json(serde_json::json!({})) + .transact() + .await + .unwrap() + .unwrap(); + + println!("Generating bundle hex"); + let bundle_hex_path = Path::new(env!("CARGO_MANIFEST_DIR")) + .join("tests") + .join("bundle_hex.txt"); + let bundle_hex = if bundle_hex_path.exists() { + println!("Loading bundle hex from file"); + std::fs::read_to_string(&bundle_hex_path) + .unwrap() + .trim() + .to_string() + } else { + println!("Generating new bundle hex"); + let hex = gen_bundle_hex(50_000); + std::fs::write(&bundle_hex_path, &hex).unwrap(); + hex + }; + + println!("Calling verify_orchard_bundle"); + let tx_status = contract + .call("verify_orchard_bundle") + .args_json(serde_json::json!({ "bundle_hex": bundle_hex })) + .gas(NearGas::from_tgas(300000)) + .transact_async() + .await + .unwrap(); + + // Manual polling loop with custom logic (e.g., timeout, backoff, logging) + let mut attempts = 0; + const MAX_ATTEMPTS: usize = 1000000; // Adjust for timeout (e.g., 100 * 300ms = 30s) + const POLL_INTERVAL: Duration = Duration::from_secs(30); + let _result = loop { + attempts += 1; + if attempts > MAX_ATTEMPTS { + panic!("Transaction did not complete within the expected time"); + } + + match tx_status.status().await.unwrap() { + std::task::Poll::Ready(result) => { + // Transaction completed + println!("Transaction finalized: {:#?}", result); + println!( + "verify_orchard_bundle total_gas_burnt: {} success={} failures={:#?}", + result.total_gas_burnt, + result.is_success(), + result.receipt_failures() + ); + break; + } + std::task::Poll::Pending => { + // Still pending, wait and retry + println!("Transaction pending, attempt {}", attempts); + println!( + "Time taken so far: {} seconds", + attempts as u64 * POLL_INTERVAL.as_secs() + ); + tokio::time::sleep(POLL_INTERVAL).await; + } + } + }; +} diff --git a/contracts/orchard-verifier/tests/gas_vk_build.rs b/contracts/orchard-verifier/tests/gas_vk_build.rs new file mode 100644 index 00000000..840e9f65 --- /dev/null +++ b/contracts/orchard-verifier/tests/gas_vk_build.rs @@ -0,0 +1,36 @@ +use near_sdk::serde_json; +use near_workspaces::network::Sandbox; +use near_workspaces::Worker; + +#[tokio::test] +async fn gas_vk_build() { + let worker: Worker = near_workspaces::sandbox().await.unwrap(); + let wasm = near_workspaces::compile_project(env!("CARGO_MANIFEST_DIR")) + .await + .expect("compile orchard verifier"); + let contract = worker.dev_deploy(&wasm).await.unwrap(); + + contract + .call("new") + .args_json(serde_json::json!({})) + .transact() + .await + .unwrap() + .unwrap(); + + let outcome = contract + .call("build_vk_only") + .args_json(serde_json::json!({})) + .max_gas() + .transact() + .await + .unwrap(); + + println!("{:#?}", outcome); + println!( + "build_vk_only total_gas_burnt: {} success={} failures={:#?}", + outcome.total_gas_burnt, + outcome.is_success(), + outcome.receipt_failures() + ); +} diff --git a/contracts/orchard-verifier/tests/setup/mod.rs b/contracts/orchard-verifier/tests/setup/mod.rs new file mode 100644 index 00000000..f185cf70 --- /dev/null +++ b/contracts/orchard-verifier/tests/setup/mod.rs @@ -0,0 +1,3 @@ +#![allow(dead_code)] +pub mod orchard; + diff --git a/contracts/orchard-verifier/tests/setup/orchard.rs b/contracts/orchard-verifier/tests/setup/orchard.rs new file mode 100644 index 00000000..aef7d123 --- /dev/null +++ b/contracts/orchard-verifier/tests/setup/orchard.rs @@ -0,0 +1,59 @@ +#![cfg(feature = "zcash")] + +use orchard::builder::{Builder, BundleType}; +use orchard::keys::{FullViewingKey, OutgoingViewingKey, Scope, SpendingKey}; +use orchard::tree::Anchor; +use orchard::value::NoteValue; +use rand::rngs::OsRng; +use zcash_address::unified::{Encoding, Receiver}; +use zcash_address::ToAddress; +use zcash_primitives::transaction::components::orchard::write_v5_bundle; + +/// Generate a Unified Address containing an Orchard receiver and a single-action +/// Orchard v5 bundle hex that is recoverable with OVK = 00..00. +pub fn gen_ua_and_orchard_bundle_hex(amount: u64, network: &str) -> (String, String) { + let mut rng = OsRng; + // Deterministic-ish recipient based on fixed SpendingKey for reproducibility + let sk = SpendingKey::from_bytes([7u8; 32]).expect("spending key"); + let fvk = FullViewingKey::from(&sk); + let recipient = fvk.address_at(0u32, Scope::External); + + // Build a simple output-only bundle with OVK = 00..00 + let mut builder = Builder::new(BundleType::DEFAULT, Anchor::empty_tree()); + builder + .add_output( + Some(OutgoingViewingKey::from([0u8; 32])), + recipient, + NoteValue::from_raw(amount), + [0u8; 512], + ) + .expect("add output"); + + let (unauth, _) = builder + .build::(&mut rng) + .expect("build orchard bundle") + .expect("bundle present"); + let pk = orchard::circuit::ProvingKey::build(); + let authorized = unauth + .create_proof(&pk, &mut rng) + .expect("create proof") + .prepare(&mut rng, [0u8; 32]) + .finalize() + .expect("finalize proof"); + + // Produce Unified Address string containing the Orchard receiver. + let orchard_raw = recipient.to_raw_address_bytes(); + let ua = zcash_address::unified::Address::try_from_items(vec![Receiver::Orchard(orchard_raw)]) + .expect("UA from orchard receiver"); + let network = match network { + "main" | "mainnet" => zcash_protocol::consensus::NetworkType::Main, + _ => zcash_protocol::consensus::NetworkType::Test, + }; + let ua_str = zcash_address::ZcashAddress::from_unified(network, ua).encode(); + + // Serialize bundle to v5 bytes and hex-encode + let mut bytes = vec![]; + write_v5_bundle(Some(&authorized), &mut bytes).expect("write v5 bundle"); + (ua_str, hex::encode(bytes)) +} + diff --git a/contracts/orchard-verifier/tests/tester.sh b/contracts/orchard-verifier/tests/tester.sh new file mode 100644 index 00000000..b3c9b07b --- /dev/null +++ b/contracts/orchard-verifier/tests/tester.sh @@ -0,0 +1,6 @@ +#! /bin/bash +export RUST_LOG=debug +export NEAR_SANDBOX_BIN_PATH=~/nearcore/target/debug/neard-sandbox + +cargo test --package orchard-verifier --test gas_parse_build --all-features -- gas_parse_build --exact --nocapture +cargo test --package orchard-verifier --test gas_verify --all-features -- gas_verify --exact --nocapture \ No newline at end of file