From c59316b4a243aea306228b2022f1567f772a4884 Mon Sep 17 00:00:00 2001 From: r4bbit <445106+0x-r4bbit@users.noreply.github.com> Date: Thu, 28 May 2026 10:35:05 +0200 Subject: [PATCH 1/2] feat(twap-oracle): implement CreatePriceObservations instruction Adds the CreatePriceObservations instruction to the TWAP oracle program. The instruction initialises a PriceObservations PDA for a given price source account and time window, writing the initial tick and timestamp as the first entry. Key design decisions: - Per-window accounts: each (price_source, window_duration) pair maps to a distinct PriceObservations PDA. The window duration is baked into the PDA seed so a single price source can support multiple TWAP windows (24h, 7d, 30d) at independent sampling rates without sharing a buffer. - window_duration not stored on struct: it is implicit in the PDA address. Any reader that located the account already knows the window duration used to derive it. Storing it would be redundant. - Authorization is implicit: the PriceObservations PDA is derived from the price source account ID, so is_authorized = true on the price source proves the caller controls it without a redundant authority field. - Impersonation is prevented by the PDA check: passing a controlled price source with a victim's observations account ID fails immediately because the computed PDA (from the attacker's source) does not match. Closes #126 --- Cargo.lock | 114 ++--- artifacts/stablecoin-idl.json | 46 +++ artifacts/twap_oracle-idl.json | 82 +++- programs/amm/methods/guest/Cargo.lock | 116 +++--- programs/ata/methods/guest/Cargo.lock | 116 +++--- programs/stablecoin/methods/guest/Cargo.lock | 113 ++--- programs/token/methods/guest/Cargo.lock | 116 +++--- programs/twap_oracle/Cargo.toml | 4 + programs/twap_oracle/core/Cargo.toml | 4 + programs/twap_oracle/core/src/lib.rs | 156 ++++++- programs/twap_oracle/methods/guest/Cargo.lock | 46 ++- programs/twap_oracle/methods/guest/Cargo.toml | 1 + .../methods/guest/src/bin/twap_oracle.rs | 30 +- .../src/create_price_observations.rs | 388 ++++++++++++++++++ programs/twap_oracle/src/lib.rs | 2 +- programs/twap_oracle/src/noop.rs | 5 - 16 files changed, 1020 insertions(+), 319 deletions(-) create mode 100644 programs/twap_oracle/src/create_price_observations.rs delete mode 100644 programs/twap_oracle/src/noop.rs diff --git a/Cargo.lock b/Cargo.lock index 9372e45..e58ef1f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -336,9 +336,9 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "base16ct" @@ -472,9 +472,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.20.2" +version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" [[package]] name = "bytemuck" @@ -599,12 +599,12 @@ dependencies = [ [[package]] name = "cipher" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e34d8227fe1ba289043aeb13792056ff80fd6de1a9f49137a5f499de8e8c78ea" +checksum = "e8cf2a2c93cd704877c0858356ed03480ff301ee950b43f1cbe4573b088bfa6c" dependencies = [ "block-buffer 0.12.0", - "crypto-common 0.2.1", + "crypto-common 0.2.2", "inout", ] @@ -701,9 +701,9 @@ dependencies = [ [[package]] name = "crypto-common" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77727bb15fa921304124b128af125e7e3b968275d1b108b379190264f4423710" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" dependencies = [ "hybrid-array", ] @@ -897,9 +897,9 @@ dependencies = [ [[package]] name = "displaydoc" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" dependencies = [ "proc-macro2", "quote", @@ -964,9 +964,9 @@ dependencies = [ [[package]] name = "either" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" [[package]] name = "elf" @@ -1305,9 +1305,9 @@ dependencies = [ [[package]] name = "http" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +checksum = "8be7462df143984c4598a256ef469b251d7d7f9e271135073e78fc535414f3d0" dependencies = [ "bytes", "itoa", @@ -1353,9 +1353,9 @@ dependencies = [ [[package]] name = "hyper" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" +checksum = "eb92f162bf56536459fc83c79b974bb12837acfed43d6bc370a7916d0ae15ecc" dependencies = [ "atomic-waker", "bytes", @@ -1644,9 +1644,9 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "js-sys" -version = "0.3.98" +version = "0.3.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67df7112613f8bfd9150013a0314e196f4800d3201ae742489d999db2f979f08" +checksum = "142bc4740e452c1e57ade0cbc129f139c9093e354346f0872ef985f4f5cf5f11" dependencies = [ "cfg-if", "futures-util", @@ -1730,9 +1730,9 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libredox" -version = "0.1.16" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" +checksum = "f02ab6bace2054fb888a3c16f990117b579d14a3088e472d63c6011fa185c9d3" dependencies = [ "libc", ] @@ -1751,9 +1751,9 @@ checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" [[package]] name = "log" -version = "0.4.29" +version = "0.4.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +checksum = "616ec5685824bcc94416c6d4a7a446eea774a31efd7062c8480ba6fd06d7a6e5" [[package]] name = "lru-slab" @@ -1772,9 +1772,9 @@ dependencies = [ [[package]] name = "maybe-async" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cf92c10c7e361d6b99666ec1c6f9805b0bea2c3bd8c78dc6fe98ac5bd78db11" +checksum = "746873a384ad60adc5db74471dfaba74bd278afbdcfd81db93fafcdfc8b5ca0c" dependencies = [ "proc-macro2", "quote", @@ -1783,9 +1783,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.8.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8" [[package]] name = "merlin" @@ -1898,9 +1898,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" [[package]] name = "num-integer" @@ -2074,7 +2074,7 @@ version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" dependencies = [ - "toml_edit 0.25.11+spec-1.1.0", + "toml_edit 0.25.12+spec-1.1.0", ] [[package]] @@ -2846,9 +2846,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.149" +version = "1.0.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" dependencies = [ "itoa", "memchr", @@ -3353,14 +3353,14 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.25.11+spec-1.1.0" +version = "0.25.12+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b" +checksum = "d2153edc6955a6c354fad8f5efd38b6a8769bdccf9fe50f8e1329f81b0baa5d7" dependencies = [ "indexmap 2.14.0", "toml_datetime 1.1.1+spec-1.1.0", "toml_parser", - "winnow 1.0.2", + "winnow 1.0.3", ] [[package]] @@ -3369,7 +3369,7 @@ version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" dependencies = [ - "winnow 1.0.2", + "winnow 1.0.3", ] [[package]] @@ -3395,9 +3395,9 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.10" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68d6fdd9f81c2819c9a8b0e0cd91660e7746a8e6ea2ba7c6b2b057985f6bcb51" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" dependencies = [ "bitflags 2.11.1", "bytes", @@ -3486,6 +3486,7 @@ version = "0.1.0" dependencies = [ "borsh", "nssa_core", + "risc0-zkvm", "serde", "spel-framework-macros", ] @@ -3494,6 +3495,7 @@ dependencies = [ name = "twap_oracle_program" version = "0.1.0" dependencies = [ + "clock_core", "nssa_core", "twap_oracle_core", ] @@ -3593,9 +3595,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.121" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49ace1d07c165b0864824eee619580c4689389afa9dc9ed3a4c75040d82e6790" +checksum = "3ed04576f974d2b2fba0f38c51dbc5518011e38c36bf1143164be765528fd409" dependencies = [ "cfg-if", "once_cell", @@ -3606,9 +3608,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.71" +version = "0.4.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96492d0d3ffba25305a7dc88720d250b1401d7edca02cc3bcd50633b424673b8" +checksum = "9473dbd2991ae90b6291c3c32c30c6187ac49aa32f9905d1cce280ec1e110b0f" dependencies = [ "js-sys", "wasm-bindgen", @@ -3616,9 +3618,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.121" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e68e6f4afd367a562002c05637acb8578ff2dea1943df76afb9e83d177c8578" +checksum = "916151b09da36bd82f6615cbf3a419e2f0ba23a03c6160e8e92eb6bd4aa1dec6" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3626,9 +3628,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.121" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d95a9ec35c64b2a7cb35d3fead40c4238d0940c86d107136999567a4703259f2" +checksum = "299047362ccbfce148b67ab7e73349f77748e00c8296f9542adfad2ad82c5c5e" dependencies = [ "bumpalo", "proc-macro2", @@ -3639,9 +3641,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.121" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4e0100b01e9f0d03189a92b96772a1fb998639d981193d7dbab487302513441" +checksum = "9a929b2c61f11ba3e9bc35b50c1f25cb38e0e892c0c231ae2b8cf78d5dad4437" dependencies = [ "unicode-ident", ] @@ -3695,9 +3697,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.98" +version = "0.3.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b572dff8bcf38bad0fa19729c89bb5748b2b9b1d8be70cf90df697e3a8f32aa" +checksum = "6d621441cfc37b84979402712047321980c178f299193a3589d05b99e8763436" dependencies = [ "js-sys", "wasm-bindgen", @@ -3948,9 +3950,9 @@ dependencies = [ [[package]] name = "winnow" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0" +checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" dependencies = [ "memchr", ] @@ -4091,18 +4093,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.48" +version = "0.8.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +checksum = "bce33a6288fa3f072a8c2c7d0f2fdbb90e28298f0135c1f99b96c3db2efcc60b" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.48" +version = "0.8.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +checksum = "8fd425244944f4ab65ccff928e7323354c5a018c75838362fdce749dfad2ee1e" dependencies = [ "proc-macro2", "quote", diff --git a/artifacts/stablecoin-idl.json b/artifacts/stablecoin-idl.json index 5c601e8..0c06a6d 100644 --- a/artifacts/stablecoin-idl.json +++ b/artifacts/stablecoin-idl.json @@ -260,6 +260,38 @@ ] } }, + { + "name": "PriceObservations", + "type": { + "kind": "struct", + "fields": [ + { + "name": "price_source_id", + "type": "account_id" + }, + { + "name": "write_index", + "type": "u32" + }, + { + "name": "total_entries", + "type": "u64" + }, + { + "name": "last_recorded_tick", + "type": "i32" + }, + { + "name": "entries", + "type": { + "vec": { + "defined": "ObservationEntry" + } + } + } + ] + } + }, { "name": "OraclePriceAccount", "type": { @@ -305,6 +337,20 @@ "name": "Expanded" } ] + }, + { + "name": "ObservationEntry", + "kind": "struct", + "fields": [ + { + "name": "timestamp", + "type": "u64" + }, + { + "name": "tick_cumulative", + "type": "i64" + } + ] } ], "instruction_type": "stablecoin_core::Instruction" diff --git a/artifacts/twap_oracle-idl.json b/artifacts/twap_oracle-idl.json index 59e2279..ee7ff93 100644 --- a/artifacts/twap_oracle-idl.json +++ b/artifacts/twap_oracle-idl.json @@ -3,12 +3,72 @@ "name": "twap_oracle", "instructions": [ { - "name": "noop", - "accounts": [], - "args": [] + "name": "create_price_observations", + "accounts": [ + { + "name": "price_observations", + "writable": false, + "signer": false, + "init": false + }, + { + "name": "price_source", + "writable": false, + "signer": false, + "init": false + }, + { + "name": "clock", + "writable": false, + "signer": false, + "init": false + } + ], + "args": [ + { + "name": "initial_tick", + "type": "i32" + }, + { + "name": "window_duration", + "type": "u64" + } + ] } ], "accounts": [ + { + "name": "PriceObservations", + "type": { + "kind": "struct", + "fields": [ + { + "name": "price_source_id", + "type": "account_id" + }, + { + "name": "write_index", + "type": "u32" + }, + { + "name": "total_entries", + "type": "u64" + }, + { + "name": "last_recorded_tick", + "type": "i32" + }, + { + "name": "entries", + "type": { + "vec": { + "defined": "ObservationEntry" + } + } + } + ] + } + }, { "name": "OraclePriceAccount", "type": { @@ -42,5 +102,21 @@ } } ], + "types": [ + { + "name": "ObservationEntry", + "kind": "struct", + "fields": [ + { + "name": "timestamp", + "type": "u64" + }, + { + "name": "tick_cumulative", + "type": "i64" + } + ] + } + ], "instruction_type": "twap_oracle_core::Instruction" } diff --git a/programs/amm/methods/guest/Cargo.lock b/programs/amm/methods/guest/Cargo.lock index 7dcd666..16a38c0 100644 --- a/programs/amm/methods/guest/Cargo.lock +++ b/programs/amm/methods/guest/Cargo.lock @@ -313,9 +313,9 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "base16ct" @@ -449,9 +449,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.20.2" +version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" [[package]] name = "bytemuck" @@ -576,12 +576,12 @@ dependencies = [ [[package]] name = "cipher" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e34d8227fe1ba289043aeb13792056ff80fd6de1a9f49137a5f499de8e8c78ea" +checksum = "e8cf2a2c93cd704877c0858356ed03480ff301ee950b43f1cbe4573b088bfa6c" dependencies = [ "block-buffer 0.12.0", - "crypto-common 0.2.1", + "crypto-common 0.2.2", "inout", ] @@ -669,9 +669,9 @@ dependencies = [ [[package]] name = "crypto-common" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77727bb15fa921304124b128af125e7e3b968275d1b108b379190264f4423710" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" dependencies = [ "hybrid-array", ] @@ -865,9 +865,9 @@ dependencies = [ [[package]] name = "displaydoc" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" dependencies = [ "proc-macro2", "quote", @@ -932,9 +932,9 @@ dependencies = [ [[package]] name = "either" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" [[package]] name = "elf" @@ -1273,9 +1273,9 @@ dependencies = [ [[package]] name = "http" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +checksum = "8be7462df143984c4598a256ef469b251d7d7f9e271135073e78fc535414f3d0" dependencies = [ "bytes", "itoa", @@ -1321,9 +1321,9 @@ dependencies = [ [[package]] name = "hyper" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" +checksum = "eb92f162bf56536459fc83c79b974bb12837acfed43d6bc370a7916d0ae15ecc" dependencies = [ "atomic-waker", "bytes", @@ -1587,9 +1587,9 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "js-sys" -version = "0.3.98" +version = "0.3.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67df7112613f8bfd9150013a0314e196f4800d3201ae742489d999db2f979f08" +checksum = "142bc4740e452c1e57ade0cbc129f139c9093e354346f0872ef985f4f5cf5f11" dependencies = [ "cfg-if", "futures-util", @@ -1673,9 +1673,9 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libredox" -version = "0.1.16" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" +checksum = "f02ab6bace2054fb888a3c16f990117b579d14a3088e472d63c6011fa185c9d3" dependencies = [ "libc", ] @@ -1694,9 +1694,9 @@ checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" [[package]] name = "log" -version = "0.4.29" +version = "0.4.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +checksum = "616ec5685824bcc94416c6d4a7a446eea774a31efd7062c8480ba6fd06d7a6e5" [[package]] name = "lru-slab" @@ -1715,9 +1715,9 @@ dependencies = [ [[package]] name = "maybe-async" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cf92c10c7e361d6b99666ec1c6f9805b0bea2c3bd8c78dc6fe98ac5bd78db11" +checksum = "746873a384ad60adc5db74471dfaba74bd278afbdcfd81db93fafcdfc8b5ca0c" dependencies = [ "proc-macro2", "quote", @@ -1726,9 +1726,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.8.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8" [[package]] name = "merlin" @@ -1819,9 +1819,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" [[package]] name = "num-integer" @@ -1995,7 +1995,7 @@ version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" dependencies = [ - "toml_edit 0.25.11+spec-1.1.0", + "toml_edit 0.25.12+spec-1.1.0", ] [[package]] @@ -2766,9 +2766,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.149" +version = "1.0.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" dependencies = [ "itoa", "memchr", @@ -3238,14 +3238,14 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.25.11+spec-1.1.0" +version = "0.25.12+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b" +checksum = "d2153edc6955a6c354fad8f5efd38b6a8769bdccf9fe50f8e1329f81b0baa5d7" dependencies = [ "indexmap 2.14.0", "toml_datetime 1.1.1+spec-1.1.0", "toml_parser", - "winnow 1.0.2", + "winnow 1.0.3", ] [[package]] @@ -3254,7 +3254,7 @@ version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" dependencies = [ - "winnow 1.0.2", + "winnow 1.0.3", ] [[package]] @@ -3280,9 +3280,9 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.10" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68d6fdd9f81c2819c9a8b0e0cd91660e7746a8e6ea2ba7c6b2b057985f6bcb51" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" dependencies = [ "bitflags 2.11.1", "bytes", @@ -3451,9 +3451,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.121" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49ace1d07c165b0864824eee619580c4689389afa9dc9ed3a4c75040d82e6790" +checksum = "3ed04576f974d2b2fba0f38c51dbc5518011e38c36bf1143164be765528fd409" dependencies = [ "cfg-if", "once_cell", @@ -3464,9 +3464,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.71" +version = "0.4.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96492d0d3ffba25305a7dc88720d250b1401d7edca02cc3bcd50633b424673b8" +checksum = "9473dbd2991ae90b6291c3c32c30c6187ac49aa32f9905d1cce280ec1e110b0f" dependencies = [ "js-sys", "wasm-bindgen", @@ -3474,9 +3474,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.121" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e68e6f4afd367a562002c05637acb8578ff2dea1943df76afb9e83d177c8578" +checksum = "916151b09da36bd82f6615cbf3a419e2f0ba23a03c6160e8e92eb6bd4aa1dec6" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3484,9 +3484,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.121" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d95a9ec35c64b2a7cb35d3fead40c4238d0940c86d107136999567a4703259f2" +checksum = "299047362ccbfce148b67ab7e73349f77748e00c8296f9542adfad2ad82c5c5e" dependencies = [ "bumpalo", "proc-macro2", @@ -3497,9 +3497,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.121" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4e0100b01e9f0d03189a92b96772a1fb998639d981193d7dbab487302513441" +checksum = "9a929b2c61f11ba3e9bc35b50c1f25cb38e0e892c0c231ae2b8cf78d5dad4437" dependencies = [ "unicode-ident", ] @@ -3553,9 +3553,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.98" +version = "0.3.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b572dff8bcf38bad0fa19729c89bb5748b2b9b1d8be70cf90df697e3a8f32aa" +checksum = "6d621441cfc37b84979402712047321980c178f299193a3589d05b99e8763436" dependencies = [ "js-sys", "wasm-bindgen", @@ -3806,9 +3806,9 @@ dependencies = [ [[package]] name = "winnow" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0" +checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" dependencies = [ "memchr", ] @@ -3949,18 +3949,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.48" +version = "0.8.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +checksum = "bce33a6288fa3f072a8c2c7d0f2fdbb90e28298f0135c1f99b96c3db2efcc60b" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.48" +version = "0.8.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +checksum = "8fd425244944f4ab65ccff928e7323354c5a018c75838362fdce749dfad2ee1e" dependencies = [ "proc-macro2", "quote", @@ -3969,9 +3969,9 @@ dependencies = [ [[package]] name = "zerofrom" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" dependencies = [ "zerofrom-derive", ] diff --git a/programs/ata/methods/guest/Cargo.lock b/programs/ata/methods/guest/Cargo.lock index ba53cf4..6dacf39 100644 --- a/programs/ata/methods/guest/Cargo.lock +++ b/programs/ata/methods/guest/Cargo.lock @@ -311,9 +311,9 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "base16ct" @@ -447,9 +447,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.20.2" +version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" [[package]] name = "bytemuck" @@ -574,12 +574,12 @@ dependencies = [ [[package]] name = "cipher" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e34d8227fe1ba289043aeb13792056ff80fd6de1a9f49137a5f499de8e8c78ea" +checksum = "e8cf2a2c93cd704877c0858356ed03480ff301ee950b43f1cbe4573b088bfa6c" dependencies = [ "block-buffer 0.12.0", - "crypto-common 0.2.1", + "crypto-common 0.2.2", "inout", ] @@ -667,9 +667,9 @@ dependencies = [ [[package]] name = "crypto-common" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77727bb15fa921304124b128af125e7e3b968275d1b108b379190264f4423710" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" dependencies = [ "hybrid-array", ] @@ -863,9 +863,9 @@ dependencies = [ [[package]] name = "displaydoc" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" dependencies = [ "proc-macro2", "quote", @@ -930,9 +930,9 @@ dependencies = [ [[package]] name = "either" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" [[package]] name = "elf" @@ -1271,9 +1271,9 @@ dependencies = [ [[package]] name = "http" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +checksum = "8be7462df143984c4598a256ef469b251d7d7f9e271135073e78fc535414f3d0" dependencies = [ "bytes", "itoa", @@ -1319,9 +1319,9 @@ dependencies = [ [[package]] name = "hyper" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" +checksum = "eb92f162bf56536459fc83c79b974bb12837acfed43d6bc370a7916d0ae15ecc" dependencies = [ "atomic-waker", "bytes", @@ -1585,9 +1585,9 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "js-sys" -version = "0.3.98" +version = "0.3.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67df7112613f8bfd9150013a0314e196f4800d3201ae742489d999db2f979f08" +checksum = "142bc4740e452c1e57ade0cbc129f139c9093e354346f0872ef985f4f5cf5f11" dependencies = [ "cfg-if", "futures-util", @@ -1671,9 +1671,9 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libredox" -version = "0.1.16" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" +checksum = "f02ab6bace2054fb888a3c16f990117b579d14a3088e472d63c6011fa185c9d3" dependencies = [ "libc", ] @@ -1692,9 +1692,9 @@ checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" [[package]] name = "log" -version = "0.4.29" +version = "0.4.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +checksum = "616ec5685824bcc94416c6d4a7a446eea774a31efd7062c8480ba6fd06d7a6e5" [[package]] name = "lru-slab" @@ -1713,9 +1713,9 @@ dependencies = [ [[package]] name = "maybe-async" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cf92c10c7e361d6b99666ec1c6f9805b0bea2c3bd8c78dc6fe98ac5bd78db11" +checksum = "746873a384ad60adc5db74471dfaba74bd278afbdcfd81db93fafcdfc8b5ca0c" dependencies = [ "proc-macro2", "quote", @@ -1724,9 +1724,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.8.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8" [[package]] name = "merlin" @@ -1817,9 +1817,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" [[package]] name = "num-integer" @@ -1993,7 +1993,7 @@ version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" dependencies = [ - "toml_edit 0.25.11+spec-1.1.0", + "toml_edit 0.25.12+spec-1.1.0", ] [[package]] @@ -2764,9 +2764,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.149" +version = "1.0.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" dependencies = [ "itoa", "memchr", @@ -3236,14 +3236,14 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.25.11+spec-1.1.0" +version = "0.25.12+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b" +checksum = "d2153edc6955a6c354fad8f5efd38b6a8769bdccf9fe50f8e1329f81b0baa5d7" dependencies = [ "indexmap 2.14.0", "toml_datetime 1.1.1+spec-1.1.0", "toml_parser", - "winnow 1.0.2", + "winnow 1.0.3", ] [[package]] @@ -3252,7 +3252,7 @@ version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" dependencies = [ - "winnow 1.0.2", + "winnow 1.0.3", ] [[package]] @@ -3278,9 +3278,9 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.10" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68d6fdd9f81c2819c9a8b0e0cd91660e7746a8e6ea2ba7c6b2b057985f6bcb51" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" dependencies = [ "bitflags 2.11.1", "bytes", @@ -3449,9 +3449,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.121" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49ace1d07c165b0864824eee619580c4689389afa9dc9ed3a4c75040d82e6790" +checksum = "3ed04576f974d2b2fba0f38c51dbc5518011e38c36bf1143164be765528fd409" dependencies = [ "cfg-if", "once_cell", @@ -3462,9 +3462,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.71" +version = "0.4.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96492d0d3ffba25305a7dc88720d250b1401d7edca02cc3bcd50633b424673b8" +checksum = "9473dbd2991ae90b6291c3c32c30c6187ac49aa32f9905d1cce280ec1e110b0f" dependencies = [ "js-sys", "wasm-bindgen", @@ -3472,9 +3472,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.121" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e68e6f4afd367a562002c05637acb8578ff2dea1943df76afb9e83d177c8578" +checksum = "916151b09da36bd82f6615cbf3a419e2f0ba23a03c6160e8e92eb6bd4aa1dec6" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3482,9 +3482,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.121" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d95a9ec35c64b2a7cb35d3fead40c4238d0940c86d107136999567a4703259f2" +checksum = "299047362ccbfce148b67ab7e73349f77748e00c8296f9542adfad2ad82c5c5e" dependencies = [ "bumpalo", "proc-macro2", @@ -3495,9 +3495,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.121" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4e0100b01e9f0d03189a92b96772a1fb998639d981193d7dbab487302513441" +checksum = "9a929b2c61f11ba3e9bc35b50c1f25cb38e0e892c0c231ae2b8cf78d5dad4437" dependencies = [ "unicode-ident", ] @@ -3551,9 +3551,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.98" +version = "0.3.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b572dff8bcf38bad0fa19729c89bb5748b2b9b1d8be70cf90df697e3a8f32aa" +checksum = "6d621441cfc37b84979402712047321980c178f299193a3589d05b99e8763436" dependencies = [ "js-sys", "wasm-bindgen", @@ -3804,9 +3804,9 @@ dependencies = [ [[package]] name = "winnow" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0" +checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" dependencies = [ "memchr", ] @@ -3947,18 +3947,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.48" +version = "0.8.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +checksum = "bce33a6288fa3f072a8c2c7d0f2fdbb90e28298f0135c1f99b96c3db2efcc60b" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.48" +version = "0.8.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +checksum = "8fd425244944f4ab65ccff928e7323354c5a018c75838362fdce749dfad2ee1e" dependencies = [ "proc-macro2", "quote", @@ -3967,9 +3967,9 @@ dependencies = [ [[package]] name = "zerofrom" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" dependencies = [ "zerofrom-derive", ] diff --git a/programs/stablecoin/methods/guest/Cargo.lock b/programs/stablecoin/methods/guest/Cargo.lock index a382086..95f28d8 100644 --- a/programs/stablecoin/methods/guest/Cargo.lock +++ b/programs/stablecoin/methods/guest/Cargo.lock @@ -278,9 +278,9 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "base16ct" @@ -414,9 +414,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.20.2" +version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" [[package]] name = "bytemuck" @@ -541,12 +541,12 @@ dependencies = [ [[package]] name = "cipher" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e34d8227fe1ba289043aeb13792056ff80fd6de1a9f49137a5f499de8e8c78ea" +checksum = "e8cf2a2c93cd704877c0858356ed03480ff301ee950b43f1cbe4573b088bfa6c" dependencies = [ "block-buffer 0.12.0", - "crypto-common 0.2.1", + "crypto-common 0.2.2", "inout", ] @@ -634,9 +634,9 @@ dependencies = [ [[package]] name = "crypto-common" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77727bb15fa921304124b128af125e7e3b968275d1b108b379190264f4423710" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" dependencies = [ "hybrid-array", ] @@ -830,9 +830,9 @@ dependencies = [ [[package]] name = "displaydoc" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" dependencies = [ "proc-macro2", "quote", @@ -897,9 +897,9 @@ dependencies = [ [[package]] name = "either" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" [[package]] name = "elf" @@ -1238,9 +1238,9 @@ dependencies = [ [[package]] name = "http" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +checksum = "8be7462df143984c4598a256ef469b251d7d7f9e271135073e78fc535414f3d0" dependencies = [ "bytes", "itoa", @@ -1286,9 +1286,9 @@ dependencies = [ [[package]] name = "hyper" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" +checksum = "eb92f162bf56536459fc83c79b974bb12837acfed43d6bc370a7916d0ae15ecc" dependencies = [ "atomic-waker", "bytes", @@ -1552,9 +1552,9 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "js-sys" -version = "0.3.98" +version = "0.3.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67df7112613f8bfd9150013a0314e196f4800d3201ae742489d999db2f979f08" +checksum = "142bc4740e452c1e57ade0cbc129f139c9093e354346f0872ef985f4f5cf5f11" dependencies = [ "cfg-if", "futures-util", @@ -1638,9 +1638,9 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libredox" -version = "0.1.16" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" +checksum = "f02ab6bace2054fb888a3c16f990117b579d14a3088e472d63c6011fa185c9d3" dependencies = [ "libc", ] @@ -1659,9 +1659,9 @@ checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" [[package]] name = "log" -version = "0.4.29" +version = "0.4.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +checksum = "616ec5685824bcc94416c6d4a7a446eea774a31efd7062c8480ba6fd06d7a6e5" [[package]] name = "lru-slab" @@ -1680,9 +1680,9 @@ dependencies = [ [[package]] name = "maybe-async" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cf92c10c7e361d6b99666ec1c6f9805b0bea2c3bd8c78dc6fe98ac5bd78db11" +checksum = "746873a384ad60adc5db74471dfaba74bd278afbdcfd81db93fafcdfc8b5ca0c" dependencies = [ "proc-macro2", "quote", @@ -1691,9 +1691,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.8.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8" [[package]] name = "merlin" @@ -1784,9 +1784,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" [[package]] name = "num-integer" @@ -1960,7 +1960,7 @@ version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" dependencies = [ - "toml_edit 0.25.11+spec-1.1.0", + "toml_edit 0.25.12+spec-1.1.0", ] [[package]] @@ -2731,9 +2731,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.149" +version = "1.0.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" dependencies = [ "itoa", "memchr", @@ -3239,14 +3239,14 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.25.11+spec-1.1.0" +version = "0.25.12+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b" +checksum = "d2153edc6955a6c354fad8f5efd38b6a8769bdccf9fe50f8e1329f81b0baa5d7" dependencies = [ "indexmap 2.14.0", "toml_datetime 1.1.1+spec-1.1.0", "toml_parser", - "winnow 1.0.2", + "winnow 1.0.3", ] [[package]] @@ -3255,7 +3255,7 @@ version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" dependencies = [ - "winnow 1.0.2", + "winnow 1.0.3", ] [[package]] @@ -3281,9 +3281,9 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.10" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68d6fdd9f81c2819c9a8b0e0cd91660e7746a8e6ea2ba7c6b2b057985f6bcb51" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" dependencies = [ "bitflags 2.11.1", "bytes", @@ -3363,6 +3363,7 @@ version = "0.1.0" dependencies = [ "borsh", "nssa_core", + "risc0-zkvm", "serde", "spel-framework-macros", ] @@ -3462,9 +3463,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.121" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49ace1d07c165b0864824eee619580c4689389afa9dc9ed3a4c75040d82e6790" +checksum = "3ed04576f974d2b2fba0f38c51dbc5518011e38c36bf1143164be765528fd409" dependencies = [ "cfg-if", "once_cell", @@ -3475,9 +3476,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.71" +version = "0.4.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96492d0d3ffba25305a7dc88720d250b1401d7edca02cc3bcd50633b424673b8" +checksum = "9473dbd2991ae90b6291c3c32c30c6187ac49aa32f9905d1cce280ec1e110b0f" dependencies = [ "js-sys", "wasm-bindgen", @@ -3485,9 +3486,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.121" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e68e6f4afd367a562002c05637acb8578ff2dea1943df76afb9e83d177c8578" +checksum = "916151b09da36bd82f6615cbf3a419e2f0ba23a03c6160e8e92eb6bd4aa1dec6" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3495,9 +3496,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.121" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d95a9ec35c64b2a7cb35d3fead40c4238d0940c86d107136999567a4703259f2" +checksum = "299047362ccbfce148b67ab7e73349f77748e00c8296f9542adfad2ad82c5c5e" dependencies = [ "bumpalo", "proc-macro2", @@ -3508,9 +3509,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.121" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4e0100b01e9f0d03189a92b96772a1fb998639d981193d7dbab487302513441" +checksum = "9a929b2c61f11ba3e9bc35b50c1f25cb38e0e892c0c231ae2b8cf78d5dad4437" dependencies = [ "unicode-ident", ] @@ -3564,9 +3565,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.98" +version = "0.3.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b572dff8bcf38bad0fa19729c89bb5748b2b9b1d8be70cf90df697e3a8f32aa" +checksum = "6d621441cfc37b84979402712047321980c178f299193a3589d05b99e8763436" dependencies = [ "js-sys", "wasm-bindgen", @@ -3817,9 +3818,9 @@ dependencies = [ [[package]] name = "winnow" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0" +checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" dependencies = [ "memchr", ] @@ -3960,18 +3961,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.48" +version = "0.8.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +checksum = "bce33a6288fa3f072a8c2c7d0f2fdbb90e28298f0135c1f99b96c3db2efcc60b" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.48" +version = "0.8.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +checksum = "8fd425244944f4ab65ccff928e7323354c5a018c75838362fdce749dfad2ee1e" dependencies = [ "proc-macro2", "quote", diff --git a/programs/token/methods/guest/Cargo.lock b/programs/token/methods/guest/Cargo.lock index 07f88f1..2be20b6 100644 --- a/programs/token/methods/guest/Cargo.lock +++ b/programs/token/methods/guest/Cargo.lock @@ -278,9 +278,9 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "base16ct" @@ -414,9 +414,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.20.2" +version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" [[package]] name = "bytemuck" @@ -541,12 +541,12 @@ dependencies = [ [[package]] name = "cipher" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e34d8227fe1ba289043aeb13792056ff80fd6de1a9f49137a5f499de8e8c78ea" +checksum = "e8cf2a2c93cd704877c0858356ed03480ff301ee950b43f1cbe4573b088bfa6c" dependencies = [ "block-buffer 0.12.0", - "crypto-common 0.2.1", + "crypto-common 0.2.2", "inout", ] @@ -634,9 +634,9 @@ dependencies = [ [[package]] name = "crypto-common" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77727bb15fa921304124b128af125e7e3b968275d1b108b379190264f4423710" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" dependencies = [ "hybrid-array", ] @@ -830,9 +830,9 @@ dependencies = [ [[package]] name = "displaydoc" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" dependencies = [ "proc-macro2", "quote", @@ -897,9 +897,9 @@ dependencies = [ [[package]] name = "either" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" [[package]] name = "elf" @@ -1238,9 +1238,9 @@ dependencies = [ [[package]] name = "http" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +checksum = "8be7462df143984c4598a256ef469b251d7d7f9e271135073e78fc535414f3d0" dependencies = [ "bytes", "itoa", @@ -1286,9 +1286,9 @@ dependencies = [ [[package]] name = "hyper" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" +checksum = "eb92f162bf56536459fc83c79b974bb12837acfed43d6bc370a7916d0ae15ecc" dependencies = [ "atomic-waker", "bytes", @@ -1552,9 +1552,9 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "js-sys" -version = "0.3.98" +version = "0.3.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67df7112613f8bfd9150013a0314e196f4800d3201ae742489d999db2f979f08" +checksum = "142bc4740e452c1e57ade0cbc129f139c9093e354346f0872ef985f4f5cf5f11" dependencies = [ "cfg-if", "futures-util", @@ -1638,9 +1638,9 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libredox" -version = "0.1.16" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" +checksum = "f02ab6bace2054fb888a3c16f990117b579d14a3088e472d63c6011fa185c9d3" dependencies = [ "libc", ] @@ -1659,9 +1659,9 @@ checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" [[package]] name = "log" -version = "0.4.29" +version = "0.4.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +checksum = "616ec5685824bcc94416c6d4a7a446eea774a31efd7062c8480ba6fd06d7a6e5" [[package]] name = "lru-slab" @@ -1680,9 +1680,9 @@ dependencies = [ [[package]] name = "maybe-async" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cf92c10c7e361d6b99666ec1c6f9805b0bea2c3bd8c78dc6fe98ac5bd78db11" +checksum = "746873a384ad60adc5db74471dfaba74bd278afbdcfd81db93fafcdfc8b5ca0c" dependencies = [ "proc-macro2", "quote", @@ -1691,9 +1691,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.8.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8" [[package]] name = "merlin" @@ -1784,9 +1784,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" [[package]] name = "num-integer" @@ -1960,7 +1960,7 @@ version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" dependencies = [ - "toml_edit 0.25.11+spec-1.1.0", + "toml_edit 0.25.12+spec-1.1.0", ] [[package]] @@ -2731,9 +2731,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.149" +version = "1.0.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" dependencies = [ "itoa", "memchr", @@ -3224,14 +3224,14 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.25.11+spec-1.1.0" +version = "0.25.12+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b" +checksum = "d2153edc6955a6c354fad8f5efd38b6a8769bdccf9fe50f8e1329f81b0baa5d7" dependencies = [ "indexmap 2.14.0", "toml_datetime 1.1.1+spec-1.1.0", "toml_parser", - "winnow 1.0.2", + "winnow 1.0.3", ] [[package]] @@ -3240,7 +3240,7 @@ version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" dependencies = [ - "winnow 1.0.2", + "winnow 1.0.3", ] [[package]] @@ -3266,9 +3266,9 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.10" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68d6fdd9f81c2819c9a8b0e0cd91660e7746a8e6ea2ba7c6b2b057985f6bcb51" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" dependencies = [ "bitflags 2.11.1", "bytes", @@ -3437,9 +3437,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.121" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49ace1d07c165b0864824eee619580c4689389afa9dc9ed3a4c75040d82e6790" +checksum = "3ed04576f974d2b2fba0f38c51dbc5518011e38c36bf1143164be765528fd409" dependencies = [ "cfg-if", "once_cell", @@ -3450,9 +3450,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.71" +version = "0.4.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96492d0d3ffba25305a7dc88720d250b1401d7edca02cc3bcd50633b424673b8" +checksum = "9473dbd2991ae90b6291c3c32c30c6187ac49aa32f9905d1cce280ec1e110b0f" dependencies = [ "js-sys", "wasm-bindgen", @@ -3460,9 +3460,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.121" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e68e6f4afd367a562002c05637acb8578ff2dea1943df76afb9e83d177c8578" +checksum = "916151b09da36bd82f6615cbf3a419e2f0ba23a03c6160e8e92eb6bd4aa1dec6" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3470,9 +3470,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.121" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d95a9ec35c64b2a7cb35d3fead40c4238d0940c86d107136999567a4703259f2" +checksum = "299047362ccbfce148b67ab7e73349f77748e00c8296f9542adfad2ad82c5c5e" dependencies = [ "bumpalo", "proc-macro2", @@ -3483,9 +3483,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.121" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4e0100b01e9f0d03189a92b96772a1fb998639d981193d7dbab487302513441" +checksum = "9a929b2c61f11ba3e9bc35b50c1f25cb38e0e892c0c231ae2b8cf78d5dad4437" dependencies = [ "unicode-ident", ] @@ -3539,9 +3539,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.98" +version = "0.3.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b572dff8bcf38bad0fa19729c89bb5748b2b9b1d8be70cf90df697e3a8f32aa" +checksum = "6d621441cfc37b84979402712047321980c178f299193a3589d05b99e8763436" dependencies = [ "js-sys", "wasm-bindgen", @@ -3792,9 +3792,9 @@ dependencies = [ [[package]] name = "winnow" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0" +checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" dependencies = [ "memchr", ] @@ -3935,18 +3935,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.48" +version = "0.8.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +checksum = "bce33a6288fa3f072a8c2c7d0f2fdbb90e28298f0135c1f99b96c3db2efcc60b" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.48" +version = "0.8.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +checksum = "8fd425244944f4ab65ccff928e7323354c5a018c75838362fdce749dfad2ee1e" dependencies = [ "proc-macro2", "quote", @@ -3955,9 +3955,9 @@ dependencies = [ [[package]] name = "zerofrom" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" dependencies = [ "zerofrom-derive", ] diff --git a/programs/twap_oracle/Cargo.toml b/programs/twap_oracle/Cargo.toml index ca8d30e..1637548 100644 --- a/programs/twap_oracle/Cargo.toml +++ b/programs/twap_oracle/Cargo.toml @@ -5,4 +5,8 @@ edition = "2021" [dependencies] nssa_core = { git = "https://github.com/logos-blockchain/logos-execution-zone.git", tag = "v0.2.0-rc3", features = ["host"] } +clock_core = { git = "https://github.com/logos-blockchain/logos-execution-zone.git", tag = "v0.2.0-rc3" } twap_oracle_core = { path = "core" } + +[lints] +workspace = true diff --git a/programs/twap_oracle/core/Cargo.toml b/programs/twap_oracle/core/Cargo.toml index c1a749f..c49d18e 100644 --- a/programs/twap_oracle/core/Cargo.toml +++ b/programs/twap_oracle/core/Cargo.toml @@ -3,8 +3,12 @@ name = "twap_oracle_core" version = "0.1.0" edition = "2021" +[lints] +workspace = true + [dependencies] nssa_core = { git = "https://github.com/logos-blockchain/logos-execution-zone.git", tag = "v0.2.0-rc3", features = ["host"] } borsh = { version = "1.5", features = ["derive"] } serde = { version = "1.0", features = ["derive"] } spel-framework-macros = { git = "https://github.com/logos-co/spel.git", tag = "v0.3.0", package = "spel-framework-macros" } +risc0-zkvm = { version = "=3.0.5", default-features = false } diff --git a/programs/twap_oracle/core/src/lib.rs b/programs/twap_oracle/core/src/lib.rs index f2ac4c0..036888f 100644 --- a/programs/twap_oracle/core/src/lib.rs +++ b/programs/twap_oracle/core/src/lib.rs @@ -1,18 +1,166 @@ use borsh::{BorshDeserialize, BorshSerialize}; -use nssa_core::account::{AccountId, Data}; +use nssa_core::{ + account::{AccountId, Data}, + program::{PdaSeed, ProgramId}, +}; use serde::{Deserialize, Serialize}; use spel_framework_macros::account_type; /// TWAP Oracle Program Instruction. #[derive(Debug, Serialize, Deserialize)] pub enum Instruction { - /// No-op instruction. Does nothing and returns no state changes. - Noop, + /// Creates and initialises a price feed account for a price source and time window. + /// + /// Required accounts (in order): + /// 1. Price feed account — uninitialized PDA derived from + /// `compute_price_observations_pda(self_program_id, price_source.account_id, + /// window_duration)`. + /// 2. Price source account — the account whose ID acts as the feed identifier (e.g. an AMM + /// pool account); must be passed with `is_authorized = true` to prove the caller controls + /// it. + /// 3. Clock account — read-only; supplies the initial observation timestamp. + CreatePriceObservations { + /// Initial price tick: `floor(log_{1.0001}(reserve_b / reserve_a))`. + initial_tick: i32, + /// Duration of the TWAP window this feed serves, in milliseconds. + /// + /// Together with `OBSERVATIONS_CAPACITY` this determines the minimum sampling interval + /// enforced by `RecordPrice`: `min_interval = window_duration / OBSERVATIONS_CAPACITY`. + /// It is also part of the PDA seed, so each window gets a distinct account. + window_duration: u64, + }, +} + +// ────────────────────────────────────────────────────────────────────────────── +// Price feed +// ────────────────────────────────────────────────────────────────────────────── + +/// Number of entries in each price feed. +/// +/// 6 396 is the maximum that fits within the `DATA_MAX_LENGTH = 100 KiB` runtime ceiling. +/// Each [`ObservationEntry`] is 16 bytes (`timestamp` 8 + `tick_cumulative` 8); fixed overhead +/// is 52 bytes (`price_source_id` 32 + `write_index` 4 + `total_entries` 8 + +/// `last_recorded_tick` 4 + Borsh `Vec` length prefix 4), leaving 102 348 bytes for entries: +/// `floor(102 348 / 16) = 6 396`. +/// +/// The effective history window depends on the `window_duration` used to derive the feed PDA +/// and the sampling guard: `min_interval = window_duration / OBSERVATIONS_CAPACITY`. A 24 h feed +/// samples every ~13 s; a 7 d feed every ~94 s; a 30 d feed every ~7 min. +pub const OBSERVATIONS_CAPACITY: u32 = 6396; + +/// A single price entry written to a [`PriceObservations`]. +#[derive( + Debug, Default, PartialEq, Eq, Clone, Serialize, Deserialize, BorshSerialize, BorshDeserialize, +)] +pub struct ObservationEntry { + /// Block timestamp (milliseconds) when this entry was recorded. + pub timestamp: u64, + /// Running sum of `tick × elapsed_seconds` up to this entry. + /// + /// Grows without bound over time, which is why this is `i64` rather than `i32`. + /// The TWAP over any window `[t1, t2]` is computed as + /// `(tick_cumulative[t2] - tick_cumulative[t1]) / (t2 - t1)`. + pub tick_cumulative: i64, +} + +/// Circular price feed of tick observations for a price source and time window. +/// +/// Owned by the TWAP oracle as a PDA derived from +/// `compute_price_observations_pda(oracle_program_id, price_source_id, window_duration)`. +/// The window duration is not stored here — it is implicit in the PDA address. Any caller +/// that locates this account already knows the window duration used to derive it. +/// Only the account that controls `price_source_id` (proven via `is_authorized = true` at call +/// time) may append new entries via `RecordPrice`. +#[account_type] +#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] +pub struct PriceObservations { + /// ID of the price source account this feed is associated with (e.g. an AMM pool). + /// The price feed PDA is derived from this ID and the window duration, so authorization is + /// implicit: whoever controls `price_source_id` is authorized to record prices. + pub price_source_id: AccountId, + /// Index of the *next* slot to write (wraps at `OBSERVATIONS_CAPACITY`). + pub write_index: u32, + /// Total entries ever appended (never resets; used to detect empty/partial-fill state). + pub total_entries: u64, + /// The raw (untruncated) tick from the most recent `RecordTick` call. + /// + /// Used by `RecordTick` to compute the tick delta for the next observation: + /// `delta = current_tick - last_recorded_tick`. Stored as the actual tick, not the + /// clamped value, so that each successive delta is computed from the true price position. + pub last_recorded_tick: i32, + /// Circular price entries; always exactly `OBSERVATIONS_CAPACITY` elements. + pub entries: Vec, +} + +impl TryFrom<&Data> for PriceObservations { + type Error = std::io::Error; + + fn try_from(data: &Data) -> Result { + Self::try_from_slice(data.as_ref()) + } +} + +impl From<&PriceObservations> for Data { + fn from(feed: &PriceObservations) -> Self { + let serialized_len = + borsh::object_length(feed).expect("PriceObservations length must be known"); + let mut data = Vec::with_capacity(serialized_len); + BorshSerialize::serialize(feed, &mut data).expect("Serialization to Vec should not fail"); + Self::try_from(data).expect("PriceObservations encoded data should fit into Data") + } +} + +// ────────────────────────────────────────────────────────────────────────────── +// PDA helpers +// ────────────────────────────────────────────────────────────────────────────── + +const PRICE_OBSERVATIONS_PDA_SEED: [u8; 32] = [2; 32]; + +/// Derives the [`AccountId`] for a price source's [`PriceObservations`] PDA. +/// +/// The `window_duration` is included in the seed so that each `(price_source, window)` pair +/// maps to a distinct account. +#[must_use] +pub fn compute_price_observations_pda( + oracle_program_id: ProgramId, + price_source_id: AccountId, + window_duration: u64, +) -> AccountId { + AccountId::for_public_pda( + &oracle_program_id, + &compute_price_observations_pda_seed(price_source_id, window_duration), + ) +} + +/// Derives the [`PdaSeed`] for a price source's [`PriceObservations`]. +/// +/// Hash input: `price_source_id (32 bytes) || window_duration_le (8 bytes) || +/// PRICE_OBSERVATIONS_PDA_SEED (32 bytes)`. +#[must_use] +pub fn compute_price_observations_pda_seed( + price_source_id: AccountId, + window_duration: u64, +) -> PdaSeed { + use risc0_zkvm::sha::{Impl, Sha256}; + + let mut bytes = [0u8; 72]; + bytes[..32].copy_from_slice(&price_source_id.to_bytes()); + bytes[32..40].copy_from_slice(&window_duration.to_le_bytes()); + bytes[40..72].copy_from_slice(&PRICE_OBSERVATIONS_PDA_SEED); + + PdaSeed::new( + Impl::hash_bytes(&bytes) + .as_bytes() + .try_into() + .expect("Hash output must be exactly 32 bytes long"), + ) } /// Canonical oracle price account consumed by LEZ programs. /// -/// Oracle producers own how this account is written; consumers only read and validate it. +/// Oracle producers own how this account is written; consumers only read and +/// validate it. The account shape is intentionally generic so that any oracle +/// type (TWAP, external adaptor, aggregator) can use the same interface. #[account_type] #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] pub struct OraclePriceAccount { diff --git a/programs/twap_oracle/methods/guest/Cargo.lock b/programs/twap_oracle/methods/guest/Cargo.lock index 31ba824..591ae6f 100644 --- a/programs/twap_oracle/methods/guest/Cargo.lock +++ b/programs/twap_oracle/methods/guest/Cargo.lock @@ -550,6 +550,15 @@ dependencies = [ "inout", ] +[[package]] +name = "clock_core" +version = "0.1.0" +source = "git+https://github.com/logos-blockchain/logos-execution-zone.git?tag=v0.2.0-rc3#cf3639d8252040d13b3d4e933feb19b42c76e14a" +dependencies = [ + "borsh", + "nssa_core", +] + [[package]] name = "cobs" version = "0.3.0" @@ -830,9 +839,9 @@ dependencies = [ [[package]] name = "displaydoc" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" dependencies = [ "proc-macro2", "quote", @@ -1238,9 +1247,9 @@ dependencies = [ [[package]] name = "http" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +checksum = "8be7462df143984c4598a256ef469b251d7d7f9e271135073e78fc535414f3d0" dependencies = [ "bytes", "itoa", @@ -1286,9 +1295,9 @@ dependencies = [ [[package]] name = "hyper" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" +checksum = "eb92f162bf56536459fc83c79b974bb12837acfed43d6bc370a7916d0ae15ecc" dependencies = [ "atomic-waker", "bytes", @@ -1638,9 +1647,9 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libredox" -version = "0.1.16" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" +checksum = "f02ab6bace2054fb888a3c16f990117b579d14a3088e472d63c6011fa185c9d3" dependencies = [ "libc", ] @@ -1691,9 +1700,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.8.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8" [[package]] name = "merlin" @@ -1960,7 +1969,7 @@ version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" dependencies = [ - "toml_edit 0.25.11+spec-1.1.0", + "toml_edit 0.25.12+spec-1.1.0", ] [[package]] @@ -3193,9 +3202,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.25.11+spec-1.1.0" +version = "0.25.12+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b" +checksum = "d2153edc6955a6c354fad8f5efd38b6a8769bdccf9fe50f8e1329f81b0baa5d7" dependencies = [ "indexmap 2.14.0", "toml_datetime 1.1.1+spec-1.1.0", @@ -3316,6 +3325,7 @@ name = "twap-oracle-guest" version = "0.1.0" dependencies = [ "borsh", + "clock_core", "nssa_core", "risc0-zkvm", "serde", @@ -3330,6 +3340,7 @@ version = "0.1.0" dependencies = [ "borsh", "nssa_core", + "risc0-zkvm", "serde", "spel-framework-macros", ] @@ -3338,6 +3349,7 @@ dependencies = [ name = "twap_oracle_program" version = "0.1.0" dependencies = [ + "clock_core", "nssa_core", "twap_oracle_core", ] @@ -3935,18 +3947,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.48" +version = "0.8.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +checksum = "bce33a6288fa3f072a8c2c7d0f2fdbb90e28298f0135c1f99b96c3db2efcc60b" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.48" +version = "0.8.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +checksum = "8fd425244944f4ab65ccff928e7323354c5a018c75838362fdce749dfad2ee1e" dependencies = [ "proc-macro2", "quote", diff --git a/programs/twap_oracle/methods/guest/Cargo.toml b/programs/twap_oracle/methods/guest/Cargo.toml index 440368d..06f7c97 100644 --- a/programs/twap_oracle/methods/guest/Cargo.toml +++ b/programs/twap_oracle/methods/guest/Cargo.toml @@ -12,6 +12,7 @@ path = "src/bin/twap_oracle.rs" [dependencies] spel-framework = { git = "https://github.com/logos-co/spel.git", tag = "v0.3.0", package = "spel-framework" } nssa_core = { git = "https://github.com/logos-blockchain/logos-execution-zone.git", tag = "v0.2.0-rc3" } +clock_core = { git = "https://github.com/logos-blockchain/logos-execution-zone.git", tag = "v0.2.0-rc3" } risc0-zkvm = { version = "=3.0.5", default-features = false } twap_oracle_core = { path = "../../core" } twap_oracle_program = { path = "../..", package = "twap_oracle_program" } diff --git a/programs/twap_oracle/methods/guest/src/bin/twap_oracle.rs b/programs/twap_oracle/methods/guest/src/bin/twap_oracle.rs index 1deb385..83be8a2 100644 --- a/programs/twap_oracle/methods/guest/src/bin/twap_oracle.rs +++ b/programs/twap_oracle/methods/guest/src/bin/twap_oracle.rs @@ -1,5 +1,7 @@ #![cfg_attr(not(test), no_main)] +use nssa_core::account::AccountWithMetadata; +use spel_framework::context::ProgramContext; use spel_framework::prelude::*; #[cfg(not(test))] @@ -10,9 +12,31 @@ mod twap_oracle { #[allow(unused_imports)] use super::*; - /// No-op instruction. Does nothing and returns no state changes. + /// Creates and initialises a price observations account for a price source and time window. + /// + /// Expected accounts: + /// 1. `price_observations` — uninitialized PDA owned by this oracle program. + /// 2. `price_source` — account the caller controls (proven via `is_authorized = true`); + /// its ID is used as the observations identifier and to derive the price observations PDA. + /// 3. `clock` — read-only LEZ clock account (any cadence). #[instruction] - pub fn noop() -> SpelResult { - Ok(spel_framework::SpelOutput::execute(twap_oracle_program::noop::noop(), vec![])) + pub fn create_price_observations( + ctx: ProgramContext, + price_observations: AccountWithMetadata, + price_source: AccountWithMetadata, + clock: AccountWithMetadata, + initial_tick: i32, + window_duration: u64, + ) -> SpelResult { + let post_states = + twap_oracle_program::create_price_observations::create_price_observations( + price_observations, + price_source, + clock, + initial_tick, + window_duration, + ctx.self_program_id, + ); + Ok(spel_framework::SpelOutput::execute(post_states, vec![])) } } diff --git a/programs/twap_oracle/src/create_price_observations.rs b/programs/twap_oracle/src/create_price_observations.rs new file mode 100644 index 0000000..9c94226 --- /dev/null +++ b/programs/twap_oracle/src/create_price_observations.rs @@ -0,0 +1,388 @@ +use clock_core::ClockAccountData; +use nssa_core::{ + account::{Account, AccountWithMetadata, Data}, + program::{AccountPostState, Claim, ProgramId}, +}; +use twap_oracle_core::{ + compute_price_observations_pda, compute_price_observations_pda_seed, ObservationEntry, + PriceObservations, OBSERVATIONS_CAPACITY, +}; + +/// Creates and initialises a [`PriceObservations`] for a price source account and time window. +/// +/// Authorization is implicit in the PDA relationship: the price observations account is derived +/// from `price_source.account_id` and `window_duration`, so whoever controls the price source +/// controls the observations account. +/// +/// # Panics +/// Panics if: +/// - `price_observations.account_id` does not match +/// `compute_price_observations_pda(oracle_program_id, price_source.account_id, window_duration)`. +/// - `price_observations.account` is not the default (already initialised). +/// - `price_source.is_authorized` is false (caller does not control the price source account). +pub fn create_price_observations( + price_observations: AccountWithMetadata, + price_source: AccountWithMetadata, + clock: AccountWithMetadata, + initial_tick: i32, + window_duration: u64, + oracle_program_id: ProgramId, +) -> Vec { + let price_source_id = price_source.account_id; + assert_eq!( + price_observations.account_id, + compute_price_observations_pda(oracle_program_id, price_source_id, window_duration), + "CreatePriceObservations: price observations account ID does not match expected PDA" + ); + assert_eq!( + price_observations.account, + Account::default(), + "CreatePriceObservations: price observations account must be uninitialized" + ); + assert!( + price_source.is_authorized, + "CreatePriceObservations: price source account must be authorized (caller must control it via a PDA)" + ); + + let clock_data = ClockAccountData::from_bytes(clock.account.data.as_ref()); + + let capacity = + usize::try_from(OBSERVATIONS_CAPACITY).expect("OBSERVATIONS_CAPACITY fits in usize"); + let mut entries = vec![ObservationEntry::default(); capacity]; + *entries + .first_mut() + .expect("OBSERVATIONS_CAPACITY is non-zero") = ObservationEntry { + timestamp: clock_data.timestamp, + tick_cumulative: 0, + }; + + let observations = PriceObservations { + price_source_id, + write_index: 1, + total_entries: 1, + last_recorded_tick: initial_tick, + entries, + }; + + let mut price_observations_post = price_observations.account.clone(); + price_observations_post.data = Data::from(&observations); + + vec![ + AccountPostState::new_claimed( + price_observations_post, + Claim::Pda(compute_price_observations_pda_seed( + price_source_id, + window_duration, + )), + ), + AccountPostState::new(price_source.account.clone()), + AccountPostState::new(clock.account.clone()), + ] +} + +#[cfg(test)] +mod tests { + use nssa_core::account::{AccountId, Nonce}; + + use super::*; + + const ORACLE_PROGRAM_ID: ProgramId = [77u32; 8]; + const CLOCK_PROGRAM_ID: ProgramId = [88u32; 8]; + /// 24-hour window in milliseconds, used as the default window for tests. + const WINDOW_24H: u64 = 24 * 60 * 60 * 1_000; + + fn price_source_id() -> AccountId { + AccountId::new([1u8; 32]) + } + + fn clock_account_with_timestamp(timestamp: u64) -> AccountWithMetadata { + let data = ClockAccountData { + block_id: 0, + timestamp, + } + .to_bytes(); + AccountWithMetadata { + account: Account { + program_owner: CLOCK_PROGRAM_ID, + balance: 0, + data: Data::try_from(data).expect("ClockAccountData fits in Data"), + nonce: Nonce(0), + }, + is_authorized: false, + account_id: AccountId::new([99u8; 32]), + } + } + + fn price_source_authorized() -> AccountWithMetadata { + AccountWithMetadata { + account: Account { + program_owner: [42u32; 8], + balance: 0, + data: Data::default(), + nonce: Nonce(0), + }, + is_authorized: true, + account_id: price_source_id(), + } + } + + fn price_observations_uninit() -> AccountWithMetadata { + AccountWithMetadata { + account: Account::default(), + is_authorized: false, + account_id: compute_price_observations_pda( + ORACLE_PROGRAM_ID, + price_source_id(), + WINDOW_24H, + ), + } + } + + // ── happy path ──────────────────────────────────────────────────────────── + + #[test] + fn returns_three_post_states() { + let post_states = create_price_observations( + price_observations_uninit(), + price_source_authorized(), + clock_account_with_timestamp(0), + 0, + WINDOW_24H, + ORACLE_PROGRAM_ID, + ); + assert_eq!(post_states.len(), 3); + } + + #[test] + fn price_observations_post_state_is_pda_claimed() { + let post_states = create_price_observations( + price_observations_uninit(), + price_source_authorized(), + clock_account_with_timestamp(0), + 0, + WINDOW_24H, + ORACLE_PROGRAM_ID, + ); + assert_eq!( + post_states[0].required_claim(), + Some(Claim::Pda(compute_price_observations_pda_seed( + price_source_id(), + WINDOW_24H + ))) + ); + } + + #[test] + fn price_source_and_clock_post_states_are_unchanged() { + let price_source = price_source_authorized(); + let clock = clock_account_with_timestamp(42_000); + let post_states = create_price_observations( + price_observations_uninit(), + price_source.clone(), + clock.clone(), + 10, + WINDOW_24H, + ORACLE_PROGRAM_ID, + ); + assert_eq!(*post_states[1].account(), price_source.account); + assert_eq!(*post_states[2].account(), clock.account); + } + + #[test] + fn initial_observation_has_zero_cumulative_and_correct_timestamp() { + let timestamp = 123_456_789u64; + + let post_states = create_price_observations( + price_observations_uninit(), + price_source_authorized(), + clock_account_with_timestamp(timestamp), + -42, + WINDOW_24H, + ORACLE_PROGRAM_ID, + ); + + let feed = PriceObservations::try_from(&post_states[0].account().data) + .expect("post state must contain a valid PriceObservations"); + + assert_eq!(feed.entries[0].tick_cumulative, 0); + assert_eq!(feed.entries[0].timestamp, timestamp); + } + + #[test] + fn initial_tick_stored_as_last_recorded_tick() { + let initial_tick = -42i32; + + let post_states = create_price_observations( + price_observations_uninit(), + price_source_authorized(), + clock_account_with_timestamp(0), + initial_tick, + WINDOW_24H, + ORACLE_PROGRAM_ID, + ); + + let feed = PriceObservations::try_from(&post_states[0].account().data) + .expect("post state must contain a valid PriceObservations"); + + assert_eq!(feed.last_recorded_tick, initial_tick); + } + + #[test] + fn write_index_and_total_entries_start_at_one() { + let post_states = create_price_observations( + price_observations_uninit(), + price_source_authorized(), + clock_account_with_timestamp(0), + 0, + WINDOW_24H, + ORACLE_PROGRAM_ID, + ); + + let feed = PriceObservations::try_from(&post_states[0].account().data) + .expect("post state must contain a valid PriceObservations"); + + assert_eq!(feed.write_index, 1); + assert_eq!(feed.total_entries, 1); + } + + #[test] + fn remaining_entries_are_default() { + let post_states = create_price_observations( + price_observations_uninit(), + price_source_authorized(), + clock_account_with_timestamp(0), + 0, + WINDOW_24H, + ORACLE_PROGRAM_ID, + ); + + let feed = PriceObservations::try_from(&post_states[0].account().data) + .expect("post state must contain a valid PriceObservations"); + + assert_eq!( + feed.entries.len(), + usize::try_from(OBSERVATIONS_CAPACITY).expect("OBSERVATIONS_CAPACITY fits in usize") + ); + assert!(feed.entries[1..] + .iter() + .all(|e| *e == ObservationEntry::default())); + } + + #[test] + fn price_source_id_stored_correctly() { + let post_states = create_price_observations( + price_observations_uninit(), + price_source_authorized(), + clock_account_with_timestamp(0), + 0, + WINDOW_24H, + ORACLE_PROGRAM_ID, + ); + + let feed = PriceObservations::try_from(&post_states[0].account().data) + .expect("post state must contain a valid PriceObservations"); + + assert_eq!(feed.price_source_id, price_source_id()); + } + + #[test] + fn different_windows_produce_distinct_pdas() { + let window_24h = 24 * 60 * 60 * 1_000u64; + let window_7d = 7 * 24 * 60 * 60 * 1_000u64; + assert_ne!( + compute_price_observations_pda(ORACLE_PROGRAM_ID, price_source_id(), window_24h), + compute_price_observations_pda(ORACLE_PROGRAM_ID, price_source_id(), window_7d), + ); + } + + #[test] + fn positive_and_negative_initial_ticks_stored_as_last_recorded_tick() { + for tick in [i32::MIN, -1, 0, 1, i32::MAX] { + let post_states = create_price_observations( + price_observations_uninit(), + price_source_authorized(), + clock_account_with_timestamp(0), + tick, + WINDOW_24H, + ORACLE_PROGRAM_ID, + ); + let feed = PriceObservations::try_from(&post_states[0].account().data) + .expect("post state must contain a valid PriceObservations"); + assert_eq!(feed.last_recorded_tick, tick); + } + } + + // ── precondition violations ─────────────────────────────────────────────── + + #[test] + #[should_panic(expected = "price observations account ID does not match expected PDA")] + fn wrong_price_feed_account_id_panics() { + let mut wrong_feed = price_observations_uninit(); + wrong_feed.account_id = AccountId::new([0u8; 32]); + create_price_observations( + wrong_feed, + price_source_authorized(), + clock_account_with_timestamp(0), + 0, + WINDOW_24H, + ORACLE_PROGRAM_ID, + ); + } + + /// An attacker who controls their own price source cannot register an observations account + /// that claims to be derived from a *different* (victim's) price source. + /// + /// The PDA derivation ties the observations account to the price source that was passed: if + /// the caller supplies their own authorized source but the victim's observations account ID, + /// the PDA check will fail because the computed PDA (from attacker's source) won't match. + #[test] + #[should_panic(expected = "price observations account ID does not match expected PDA")] + fn cannot_register_observations_for_another_price_source() { + let victim_source_id = AccountId::new([2u8; 32]); + // The attacker passes the victim's observations PDA as the target account… + let victim_observations_pda = + compute_price_observations_pda(ORACLE_PROGRAM_ID, victim_source_id, WINDOW_24H); + let mut attacker_observations = price_observations_uninit(); + attacker_observations.account_id = victim_observations_pda; + + // …but only controls their own price source (price_source_id = [1u8; 32]). + create_price_observations( + attacker_observations, + price_source_authorized(), + clock_account_with_timestamp(0), + 0, + WINDOW_24H, + ORACLE_PROGRAM_ID, + ); + } + + #[test] + #[should_panic(expected = "price observations account must be uninitialized")] + fn already_initialized_price_feed_panics() { + let mut initialized_feed = price_observations_uninit(); + initialized_feed.account.data = Data::try_from(vec![1u8; 10]).expect("fits in Data"); + create_price_observations( + initialized_feed, + price_source_authorized(), + clock_account_with_timestamp(0), + 0, + WINDOW_24H, + ORACLE_PROGRAM_ID, + ); + } + + #[test] + #[should_panic(expected = "price source account must be authorized")] + fn unauthorized_price_source_panics() { + let mut unauthorized = price_source_authorized(); + unauthorized.is_authorized = false; + create_price_observations( + price_observations_uninit(), + unauthorized, + clock_account_with_timestamp(0), + 0, + WINDOW_24H, + ORACLE_PROGRAM_ID, + ); + } +} diff --git a/programs/twap_oracle/src/lib.rs b/programs/twap_oracle/src/lib.rs index 221b2f9..2e60510 100644 --- a/programs/twap_oracle/src/lib.rs +++ b/programs/twap_oracle/src/lib.rs @@ -2,4 +2,4 @@ pub use twap_oracle_core as core; -pub mod noop; +pub mod create_price_observations; diff --git a/programs/twap_oracle/src/noop.rs b/programs/twap_oracle/src/noop.rs deleted file mode 100644 index a2eadea..0000000 --- a/programs/twap_oracle/src/noop.rs +++ /dev/null @@ -1,5 +0,0 @@ -use nssa_core::program::AccountPostState; - -pub fn noop() -> Vec { - vec![] -} From bb55bd3ccdd8369a21d95b47fbb8bb1c9ee4424f Mon Sep 17 00:00:00 2001 From: r4bbit <445106+0x-r4bbit@users.noreply.github.com> Date: Thu, 28 May 2026 20:42:59 +0200 Subject: [PATCH 2/2] feat(twap-oracle): implement CreateOraclePriceAccount instruction MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds the CreateOraclePriceAccount instruction to the TWAP oracle program. The instruction initialises a canonical OraclePriceAccount PDA for a given price source and time window. The account starts with price = 0 and timestamp = 0 — a deliberately invalid sentinel state that signals "not yet published". Consumers are expected to reject any account whose timestamp is zero or stale, so the transient invalid state requires no special on-chain enforcement. - PDA mirrors PriceObservations: derived from (oracle_program_id, price_source_id, window_duration) with a distinct seed constant, so each (source, window) pair maps to a distinct oracle price account that cannot collide with its corresponding observations account. - source_id is not a parameter: it is always set to price_source.account_id. Accepting it as a free parameter would allow callers to register a price account that claims to represent a source it does not control. Deriving it from the authorized price source account closes that vector entirely. - Authorization follows the same model as CreatePriceObservations: is_authorized = true on the price source proves the caller controls it; the PDA check ensures the supplied oracle price account address is the one derived from that specific source and window. - price = 0 / timestamp = 0 is the correct initial state: coupling account creation to first publication would require the observation account to already hold a full window of ticks, blocking registration for up to the window duration. Consumers must validate oracle prices regardless, so the zero sentinel falls naturally out of the staleness check they already own. Closes #129 --- artifacts/stablecoin-idl.json | 26 +- artifacts/twap_oracle-idl.json | 33 +- programs/twap_oracle/core/src/lib.rs | 75 +++- .../methods/guest/src/bin/twap_oracle.rs | 31 +- .../src/create_oracle_price_account.rs | 369 ++++++++++++++++++ programs/twap_oracle/src/lib.rs | 1 + 6 files changed, 516 insertions(+), 19 deletions(-) create mode 100644 programs/twap_oracle/src/create_oracle_price_account.rs diff --git a/artifacts/stablecoin-idl.json b/artifacts/stablecoin-idl.json index 0c06a6d..f90a1a5 100644 --- a/artifacts/stablecoin-idl.json +++ b/artifacts/stablecoin-idl.json @@ -315,7 +315,7 @@ }, { "name": "source_id", - "type": "string" + "type": "account_id" }, { "name": "confidence_interval", @@ -326,18 +326,6 @@ } ], "types": [ - { - "name": "MetadataStandard", - "kind": "enum", - "variants": [ - { - "name": "Simple" - }, - { - "name": "Expanded" - } - ] - }, { "name": "ObservationEntry", "kind": "struct", @@ -351,6 +339,18 @@ "type": "i64" } ] + }, + { + "name": "MetadataStandard", + "kind": "enum", + "variants": [ + { + "name": "Simple" + }, + { + "name": "Expanded" + } + ] } ], "instruction_type": "stablecoin_core::Instruction" diff --git a/artifacts/twap_oracle-idl.json b/artifacts/twap_oracle-idl.json index ee7ff93..df340bc 100644 --- a/artifacts/twap_oracle-idl.json +++ b/artifacts/twap_oracle-idl.json @@ -34,6 +34,37 @@ "type": "u64" } ] + }, + { + "name": "create_oracle_price_account", + "accounts": [ + { + "name": "oracle_price_account", + "writable": false, + "signer": false, + "init": false + }, + { + "name": "price_source", + "writable": false, + "signer": false, + "init": false + } + ], + "args": [ + { + "name": "base_asset", + "type": "account_id" + }, + { + "name": "quote_asset", + "type": "account_id" + }, + { + "name": "window_duration", + "type": "u64" + } + ] } ], "accounts": [ @@ -92,7 +123,7 @@ }, { "name": "source_id", - "type": "string" + "type": "account_id" }, { "name": "confidence_interval", diff --git a/programs/twap_oracle/core/src/lib.rs b/programs/twap_oracle/core/src/lib.rs index 036888f..964593b 100644 --- a/programs/twap_oracle/core/src/lib.rs +++ b/programs/twap_oracle/core/src/lib.rs @@ -9,10 +9,10 @@ use spel_framework_macros::account_type; /// TWAP Oracle Program Instruction. #[derive(Debug, Serialize, Deserialize)] pub enum Instruction { - /// Creates and initialises a price feed account for a price source and time window. + /// Creates and initialises a price observations account for a price source and time window. /// /// Required accounts (in order): - /// 1. Price feed account — uninitialized PDA derived from + /// 1. Price observations account — uninitialized PDA derived from /// `compute_price_observations_pda(self_program_id, price_source.account_id, /// window_duration)`. /// 2. Price source account — the account whose ID acts as the feed identifier (e.g. an AMM @@ -29,6 +29,30 @@ pub enum Instruction { /// It is also part of the PDA seed, so each window gets a distinct account. window_duration: u64, }, + /// Creates and initialises a canonical [`OraclePriceAccount`] for a price source and time + /// window. + /// + /// The price and timestamp start at zero and are populated later by a `PublishPrice` + /// instruction. Consumers must reject accounts whose `timestamp` is zero or stale. + /// + /// Required accounts (in order): + /// 1. Oracle price account — uninitialized PDA derived from + /// `compute_oracle_price_account_pda(self_program_id, price_source.account_id, + /// window_duration)`. + /// 2. Price source account — must be passed with `is_authorized = true` to prove the caller + /// controls it. Its ID ties this price account to the same source as the corresponding + /// [`PriceObservations`] account for the same window. + CreateOraclePriceAccount { + /// Canonical identifier of the base asset being priced. + base_asset: AccountId, + /// Canonical identifier of the quote asset that denominates `price`. + quote_asset: AccountId, + /// Duration of the TWAP window this price account serves, in milliseconds. + /// + /// Part of the PDA seed, so each `(price_source, window)` pair maps to a distinct + /// oracle price account. + window_duration: u64, + }, } // ────────────────────────────────────────────────────────────────────────────── @@ -156,6 +180,48 @@ pub fn compute_price_observations_pda_seed( ) } +const ORACLE_PRICE_ACCOUNT_PDA_SEED: [u8; 32] = [3; 32]; + +/// Derives the [`AccountId`] for a price source's [`OraclePriceAccount`] PDA. +/// +/// The `window_duration` is included in the seed so that each `(price_source, window)` pair +/// maps to a distinct account, mirroring the [`PriceObservations`] PDA derivation. +#[must_use] +pub fn compute_oracle_price_account_pda( + oracle_program_id: ProgramId, + price_source_id: AccountId, + window_duration: u64, +) -> AccountId { + AccountId::for_public_pda( + &oracle_program_id, + &compute_oracle_price_account_pda_seed(price_source_id, window_duration), + ) +} + +/// Derives the [`PdaSeed`] for a price source's [`OraclePriceAccount`]. +/// +/// Hash input: `price_source_id (32 bytes) || window_duration_le (8 bytes) || +/// ORACLE_PRICE_ACCOUNT_PDA_SEED (32 bytes)`. +#[must_use] +pub fn compute_oracle_price_account_pda_seed( + price_source_id: AccountId, + window_duration: u64, +) -> PdaSeed { + use risc0_zkvm::sha::{Impl, Sha256}; + + let mut bytes = [0u8; 72]; + bytes[..32].copy_from_slice(&price_source_id.to_bytes()); + bytes[32..40].copy_from_slice(&window_duration.to_le_bytes()); + bytes[40..72].copy_from_slice(&ORACLE_PRICE_ACCOUNT_PDA_SEED); + + PdaSeed::new( + Impl::hash_bytes(&bytes) + .as_bytes() + .try_into() + .expect("Hash output must be exactly 32 bytes long"), + ) +} + /// Canonical oracle price account consumed by LEZ programs. /// /// Oracle producers own how this account is written; consumers only read and @@ -175,8 +241,9 @@ pub struct OraclePriceAccount { /// Price observation timestamp. Consumers choose the time unit by matching this with /// `max_age`. pub timestamp: u64, - /// Identifier of the source that populated this account, such as a TWAP or external adaptor. - pub source_id: String, + /// Identifier of the source account that populated this account, such as a TWAP program or + /// external adaptor. + pub source_id: AccountId, /// Source-provided confidence interval, or zero when the source does not provide one. pub confidence_interval: u128, } diff --git a/programs/twap_oracle/methods/guest/src/bin/twap_oracle.rs b/programs/twap_oracle/methods/guest/src/bin/twap_oracle.rs index 83be8a2..5f3f096 100644 --- a/programs/twap_oracle/methods/guest/src/bin/twap_oracle.rs +++ b/programs/twap_oracle/methods/guest/src/bin/twap_oracle.rs @@ -1,6 +1,6 @@ #![cfg_attr(not(test), no_main)] -use nssa_core::account::AccountWithMetadata; +use nssa_core::account::{AccountId, AccountWithMetadata}; use spel_framework::context::ProgramContext; use spel_framework::prelude::*; @@ -39,4 +39,33 @@ mod twap_oracle { ); Ok(spel_framework::SpelOutput::execute(post_states, vec![])) } + + /// Creates and initialises a canonical oracle price account for a price source and time + /// window. + /// + /// Expected accounts: + /// 1. `oracle_price_account` — uninitialized PDA owned by this oracle program. + /// 2. `price_source` — account the caller controls (proven via `is_authorized = true`); + /// its ID ties this price account to the same source as the corresponding + /// `PriceObservations` account for the same window. + #[instruction] + pub fn create_oracle_price_account( + ctx: ProgramContext, + oracle_price_account: AccountWithMetadata, + price_source: AccountWithMetadata, + base_asset: AccountId, + quote_asset: AccountId, + window_duration: u64, + ) -> SpelResult { + let post_states = + twap_oracle_program::create_oracle_price_account::create_oracle_price_account( + oracle_price_account, + price_source, + base_asset, + quote_asset, + window_duration, + ctx.self_program_id, + ); + Ok(spel_framework::SpelOutput::execute(post_states, vec![])) + } } diff --git a/programs/twap_oracle/src/create_oracle_price_account.rs b/programs/twap_oracle/src/create_oracle_price_account.rs new file mode 100644 index 0000000..75b4efb --- /dev/null +++ b/programs/twap_oracle/src/create_oracle_price_account.rs @@ -0,0 +1,369 @@ +use nssa_core::{ + account::{Account, AccountId, AccountWithMetadata, Data}, + program::{AccountPostState, Claim, ProgramId}, +}; +use twap_oracle_core::{ + compute_oracle_price_account_pda, compute_oracle_price_account_pda_seed, OraclePriceAccount, +}; + +/// Creates and initialises an [`OraclePriceAccount`] for a price source account and time window. +/// +/// The account is initialised with `price = 0`, `timestamp = 0`, and `confidence_interval = 0`. +/// These are populated later by a `PublishPrice` instruction. Consumers must reject accounts +/// whose `timestamp` is zero or stale. +/// +/// Authorization is implicit in the PDA relationship: the oracle price account is derived from +/// `price_source.account_id` and `window_duration`, so whoever controls the price source +/// controls this account. +/// +/// # Panics +/// Panics if: +/// - `oracle_price_account.account_id` does not match +/// `compute_oracle_price_account_pda(oracle_program_id, price_source.account_id, +/// window_duration)`. +/// - `oracle_price_account.account` is not the default (already initialised). +/// - `price_source.is_authorized` is false (caller does not control the price source account). +pub fn create_oracle_price_account( + oracle_price_account: AccountWithMetadata, + price_source: AccountWithMetadata, + base_asset: AccountId, + quote_asset: AccountId, + window_duration: u64, + oracle_program_id: ProgramId, +) -> Vec { + let price_source_id = price_source.account_id; + assert_eq!( + oracle_price_account.account_id, + compute_oracle_price_account_pda(oracle_program_id, price_source_id, window_duration), + "CreateOraclePriceAccount: oracle price account ID does not match expected PDA" + ); + assert_eq!( + oracle_price_account.account, + Account::default(), + "CreateOraclePriceAccount: oracle price account must be uninitialized" + ); + assert!( + price_source.is_authorized, + "CreateOraclePriceAccount: price source account must be authorized (caller must control it via a PDA)" + ); + + let account = OraclePriceAccount { + base_asset, + quote_asset, + price: 0, + timestamp: 0, + source_id: price_source_id, + confidence_interval: 0, + }; + + let mut oracle_price_account_post = oracle_price_account.account.clone(); + oracle_price_account_post.data = Data::from(&account); + + vec![ + AccountPostState::new_claimed( + oracle_price_account_post, + Claim::Pda(compute_oracle_price_account_pda_seed( + price_source_id, + window_duration, + )), + ), + AccountPostState::new(price_source.account.clone()), + ] +} + +#[cfg(test)] +mod tests { + use nssa_core::account::Nonce; + + use super::*; + + const ORACLE_PROGRAM_ID: ProgramId = [77u32; 8]; + /// 24-hour window in milliseconds. + const WINDOW_24H: u64 = 24 * 60 * 60 * 1_000; + + fn price_source_id() -> AccountId { + AccountId::new([1u8; 32]) + } + + fn base_asset() -> AccountId { + AccountId::new([10u8; 32]) + } + + fn quote_asset() -> AccountId { + AccountId::new([11u8; 32]) + } + + fn price_source_authorized() -> AccountWithMetadata { + AccountWithMetadata { + account: Account { + program_owner: [42u32; 8], + balance: 0, + data: Data::default(), + nonce: Nonce(0), + }, + is_authorized: true, + account_id: price_source_id(), + } + } + + fn oracle_price_account_uninit() -> AccountWithMetadata { + AccountWithMetadata { + account: Account::default(), + is_authorized: false, + account_id: compute_oracle_price_account_pda( + ORACLE_PROGRAM_ID, + price_source_id(), + WINDOW_24H, + ), + } + } + + // ── happy path ──────────────────────────────────────────────────────────── + + #[test] + fn returns_two_post_states() { + let post_states = create_oracle_price_account( + oracle_price_account_uninit(), + price_source_authorized(), + base_asset(), + quote_asset(), + WINDOW_24H, + ORACLE_PROGRAM_ID, + ); + assert_eq!(post_states.len(), 2); + } + + #[test] + fn oracle_price_account_post_state_is_pda_claimed() { + let post_states = create_oracle_price_account( + oracle_price_account_uninit(), + price_source_authorized(), + base_asset(), + quote_asset(), + WINDOW_24H, + ORACLE_PROGRAM_ID, + ); + assert_eq!( + post_states[0].required_claim(), + Some(Claim::Pda(compute_oracle_price_account_pda_seed( + price_source_id(), + WINDOW_24H, + ))) + ); + } + + #[test] + fn price_source_post_state_is_unchanged() { + let price_source = price_source_authorized(); + let post_states = create_oracle_price_account( + oracle_price_account_uninit(), + price_source.clone(), + base_asset(), + quote_asset(), + WINDOW_24H, + ORACLE_PROGRAM_ID, + ); + assert_eq!(*post_states[1].account(), price_source.account); + } + + #[test] + fn account_initialised_with_zero_price_and_timestamp() { + let post_states = create_oracle_price_account( + oracle_price_account_uninit(), + price_source_authorized(), + base_asset(), + quote_asset(), + WINDOW_24H, + ORACLE_PROGRAM_ID, + ); + let account = OraclePriceAccount::try_from(&post_states[0].account().data) + .expect("post state must contain a valid OraclePriceAccount"); + assert_eq!(account.price, 0); + assert_eq!(account.timestamp, 0); + assert_eq!(account.confidence_interval, 0); + } + + #[test] + fn assets_and_source_id_stored_correctly() { + let post_states = create_oracle_price_account( + oracle_price_account_uninit(), + price_source_authorized(), + base_asset(), + quote_asset(), + WINDOW_24H, + ORACLE_PROGRAM_ID, + ); + let account = OraclePriceAccount::try_from(&post_states[0].account().data) + .expect("post state must contain a valid OraclePriceAccount"); + assert_eq!(account.base_asset, base_asset()); + assert_eq!(account.quote_asset, quote_asset()); + assert_eq!(account.source_id, price_source_id()); + } + + /// `source_id` must always equal the price source's `account_id`, regardless of which + /// price source is used. This test uses a distinct source ID to make the invariant explicit. + #[test] + fn source_id_equals_price_source_account_id() { + let other_source_id = AccountId::new([99u8; 32]); + let other_source = AccountWithMetadata { + account: Account { + program_owner: [42u32; 8], + balance: 0, + data: Data::default(), + nonce: Nonce(0), + }, + is_authorized: true, + account_id: other_source_id, + }; + let other_price_account = AccountWithMetadata { + account: Account::default(), + is_authorized: false, + account_id: compute_oracle_price_account_pda( + ORACLE_PROGRAM_ID, + other_source_id, + WINDOW_24H, + ), + }; + let post_states = create_oracle_price_account( + other_price_account, + other_source, + base_asset(), + quote_asset(), + WINDOW_24H, + ORACLE_PROGRAM_ID, + ); + let account = OraclePriceAccount::try_from(&post_states[0].account().data) + .expect("post state must contain a valid OraclePriceAccount"); + assert_eq!(account.source_id, other_source_id); + } + + #[test] + fn different_price_sources_produce_distinct_pdas() { + let other_source_id = AccountId::new([2u8; 32]); + assert_ne!( + compute_oracle_price_account_pda(ORACLE_PROGRAM_ID, price_source_id(), WINDOW_24H), + compute_oracle_price_account_pda(ORACLE_PROGRAM_ID, other_source_id, WINDOW_24H), + ); + } + + #[test] + fn different_windows_produce_distinct_pdas() { + let window_7d = 7 * 24 * 60 * 60 * 1_000u64; + assert_ne!( + compute_oracle_price_account_pda(ORACLE_PROGRAM_ID, price_source_id(), WINDOW_24H), + compute_oracle_price_account_pda(ORACLE_PROGRAM_ID, price_source_id(), window_7d), + ); + } + + #[test] + fn oracle_price_account_pda_differs_from_price_observations_pda() { + use twap_oracle_core::compute_price_observations_pda; + assert_ne!( + compute_oracle_price_account_pda(ORACLE_PROGRAM_ID, price_source_id(), WINDOW_24H), + compute_price_observations_pda(ORACLE_PROGRAM_ID, price_source_id(), WINDOW_24H), + ); + } + + /// A plain wallet account (no program owner, no data) can act as the price source just as + /// well as a program-owned PDA. Authorization is conveyed via `is_authorized = true` + /// regardless of account type. + #[test] + fn wallet_account_as_price_source_works() { + let wallet_id = AccountId::new([55u8; 32]); + let wallet = AccountWithMetadata { + account: Account { + program_owner: [0u32; 8], + balance: 1_000, + data: Data::default(), + nonce: Nonce(0), + }, + is_authorized: true, + account_id: wallet_id, + }; + let price_account = AccountWithMetadata { + account: Account::default(), + is_authorized: false, + account_id: compute_oracle_price_account_pda(ORACLE_PROGRAM_ID, wallet_id, WINDOW_24H), + }; + let post_states = create_oracle_price_account( + price_account, + wallet, + base_asset(), + quote_asset(), + WINDOW_24H, + ORACLE_PROGRAM_ID, + ); + let account = OraclePriceAccount::try_from(&post_states[0].account().data) + .expect("post state must contain a valid OraclePriceAccount"); + assert_eq!(account.source_id, wallet_id); + assert_eq!(account.base_asset, base_asset()); + assert_eq!(account.quote_asset, quote_asset()); + } + + // ── precondition violations ─────────────────────────────────────────────── + + #[test] + #[should_panic(expected = "oracle price account ID does not match expected PDA")] + fn wrong_oracle_price_account_id_panics() { + let mut wrong = oracle_price_account_uninit(); + wrong.account_id = AccountId::new([0u8; 32]); + create_oracle_price_account( + wrong, + price_source_authorized(), + base_asset(), + quote_asset(), + WINDOW_24H, + ORACLE_PROGRAM_ID, + ); + } + + #[test] + #[should_panic(expected = "oracle price account must be uninitialized")] + fn already_initialized_oracle_price_account_panics() { + let mut initialized = oracle_price_account_uninit(); + initialized.account.data = Data::try_from(vec![1u8; 10]).expect("fits in Data"); + create_oracle_price_account( + initialized, + price_source_authorized(), + base_asset(), + quote_asset(), + WINDOW_24H, + ORACLE_PROGRAM_ID, + ); + } + + #[test] + #[should_panic(expected = "price source account must be authorized")] + fn unauthorized_price_source_panics() { + let mut unauthorized = price_source_authorized(); + unauthorized.is_authorized = false; + create_oracle_price_account( + oracle_price_account_uninit(), + unauthorized, + base_asset(), + quote_asset(), + WINDOW_24H, + ORACLE_PROGRAM_ID, + ); + } + + /// An attacker who controls their own price source cannot register an oracle price account + /// that claims to be derived from a different (victim's) price source. + #[test] + #[should_panic(expected = "oracle price account ID does not match expected PDA")] + fn cannot_register_price_account_for_another_price_source() { + let victim_source_id = AccountId::new([2u8; 32]); + let victim_pda = + compute_oracle_price_account_pda(ORACLE_PROGRAM_ID, victim_source_id, WINDOW_24H); + let mut attacker_account = oracle_price_account_uninit(); + attacker_account.account_id = victim_pda; + create_oracle_price_account( + attacker_account, + price_source_authorized(), + base_asset(), + quote_asset(), + WINDOW_24H, + ORACLE_PROGRAM_ID, + ); + } +} diff --git a/programs/twap_oracle/src/lib.rs b/programs/twap_oracle/src/lib.rs index 2e60510..e029dea 100644 --- a/programs/twap_oracle/src/lib.rs +++ b/programs/twap_oracle/src/lib.rs @@ -2,4 +2,5 @@ pub use twap_oracle_core as core; +pub mod create_oracle_price_account; pub mod create_price_observations;