├── program ├── rust │ ├── .gitignore │ ├── test_data │ │ ├── aggregation │ │ │ ├── 38.result │ │ │ ├── 1.result │ │ │ ├── 14.result │ │ │ ├── 18.result │ │ │ ├── 19.result │ │ │ ├── 21.result │ │ │ ├── 23.result │ │ │ ├── 24.result │ │ │ ├── 25.result │ │ │ ├── 26.result │ │ │ ├── 27.result │ │ │ ├── 28.result │ │ │ ├── 29.result │ │ │ ├── 3.result │ │ │ ├── 30.result │ │ │ ├── 4.result │ │ │ ├── 5.result │ │ │ ├── 6.result │ │ │ ├── 8.result │ │ │ ├── 9.result │ │ │ ├── 12.result │ │ │ ├── 13.result │ │ │ ├── 15.result │ │ │ ├── 16.result │ │ │ ├── 17.result │ │ │ ├── 2.result │ │ │ ├── 20.result │ │ │ ├── 22.result │ │ │ ├── 7.result │ │ │ ├── 10.result │ │ │ ├── 39.result │ │ │ ├── 31.result │ │ │ ├── 32.result │ │ │ ├── 33.result │ │ │ ├── 34.result │ │ │ ├── 36.result │ │ │ ├── 11.result │ │ │ ├── 35.result │ │ │ ├── 37.result │ │ │ ├── 3.json │ │ │ ├── 8.json │ │ │ ├── 1.json │ │ │ ├── 18.json │ │ │ ├── 4.json │ │ │ ├── 5.json │ │ │ ├── 6.json │ │ │ ├── 7.json │ │ │ ├── 2.json │ │ │ ├── 10.json │ │ │ ├── 22.json │ │ │ ├── 11.json │ │ │ ├── 39.json │ │ │ ├── 9.json │ │ │ ├── 19.json │ │ │ ├── 12.json │ │ │ ├── 38.json │ │ │ ├── 14.json │ │ │ ├── 20.json │ │ │ ├── 16.json │ │ │ ├── 17.json │ │ │ ├── 15.json │ │ │ ├── 21.json │ │ │ ├── 36.json │ │ │ ├── 35.json │ │ │ ├── 37.json │ │ │ ├── 33.json │ │ │ ├── 32.json │ │ │ ├── 23.json │ │ │ ├── 24.json │ │ │ ├── 25.json │ │ │ ├── 26.json │ │ │ ├── 27.json │ │ │ ├── 28.json │ │ │ ├── 29.json │ │ │ ├── 30.json │ │ │ ├── 31.json │ │ │ ├── 13.json │ │ │ └── 34.json │ │ ├── ema │ │ │ ├── small_conf.csv │ │ │ ├── small_conf.result │ │ │ ├── price10_conf1.csv │ │ │ ├── audit_overflow.csv │ │ │ ├── price10_conf1.result │ │ │ ├── 1.csv │ │ │ ├── audit_overflow.result │ │ │ ├── 1.result │ │ │ ├── price10_conf2.csv │ │ │ └── price11_conf1.csv │ │ └── publish │ │ │ └── products.json │ ├── src │ │ ├── bindings.h │ │ ├── c_oracle_header.rs │ │ ├── tests │ │ │ ├── mod.rs │ │ │ ├── test_c_code.rs │ │ │ ├── test_resize_mapping.rs │ │ │ ├── test_message.rs │ │ │ ├── test_del_price.rs │ │ │ ├── test_check_valid_signable_account_or_permissioned_funding_account.rs │ │ │ ├── test_set_min_pub.rs │ │ │ ├── test_set_max_latency.rs │ │ │ ├── test_full_publisher_set.rs │ │ │ ├── test_del_product.rs │ │ │ └── test_aggregation.rs │ │ ├── accounts │ │ │ ├── mapping.rs │ │ │ └── permission.rs │ │ ├── processor │ │ │ ├── init_mapping.rs │ │ │ ├── set_min_pub.rs │ │ │ ├── set_max_latency.rs │ │ │ ├── upd_product.rs │ │ │ ├── resize_mapping.rs │ │ │ ├── del_publisher.rs │ │ │ ├── upd_permissions.rs │ │ │ ├── add_product.rs │ │ │ ├── init_price.rs │ │ │ ├── del_price.rs │ │ │ └── add_price.rs │ │ ├── lib.rs │ │ ├── error.rs │ │ └── deserialize.rs │ └── Cargo.toml └── c │ ├── src │ └── oracle │ │ ├── util │ │ ├── util.h │ │ ├── test_round.c │ │ ├── compat_stdint.h │ │ ├── align.h │ │ ├── test_hash.c │ │ ├── test_sar.c │ │ ├── sar.h │ │ ├── hash.h │ │ └── test_align.c │ │ ├── upd_aggregate.c │ │ ├── native │ │ └── upd_aggregate.c │ │ ├── sort │ │ ├── tmpl │ │ │ └── sort_stable_base.c │ │ └── test_sort_stable.c │ │ └── model │ │ └── test_price_model.c │ └── makefile ├── .dockerignore ├── pyth └── tests │ └── fuzz │ └── add │ └── testcases │ └── input.txt ├── Cargo.toml ├── rust-toolchain ├── .gitignore ├── scripts ├── patch-solana.sh ├── check-size.sh ├── start-docker-build-env.sh ├── check-docker-pin.sh ├── build.sh ├── solana.patch ├── get-bpf-tools.sh └── build-bpf.sh ├── .github └── workflows │ └── check-fomatting.yml ├── rustfmt.toml ├── dashboard ├── style.css └── index.html ├── .pre-commit-config.yaml ├── pc ├── mem_map.hpp ├── error.hpp ├── capture.hpp ├── replay.hpp ├── log.hpp ├── pub_stats.cpp ├── pub_stats.hpp ├── mem_map.cpp ├── replay.cpp ├── key_store.hpp ├── user.hpp ├── dbl_list.hpp ├── misc.hpp ├── hash_map.hpp └── key_pair.hpp ├── docker ├── fuzz │ └── Dockerfile └── Dockerfile ├── pctest ├── test_error.hpp ├── test_products.json ├── init_key_store.sh ├── setup_pub.sh ├── setup_local.sh └── test_mapping.cpp ├── doc └── cpp-batching-upgrade.md ├── pcapps └── tx_rpc_client.hpp ├── DEPEND.md └── CHANGELOG.md /program/rust/.gitignore: -------------------------------------------------------------------------------- 1 | codegen/* 2 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | build 2 | target 3 | program/rust/codegen -------------------------------------------------------------------------------- /pyth/tests/fuzz/add/testcases/input.txt: -------------------------------------------------------------------------------- 1 | 1 2 | 1 3 | 1 4 | 1 5 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = [ 4 | "program/rust" 5 | ] 6 | 7 | -------------------------------------------------------------------------------- /program/rust/test_data/aggregation/38.result: -------------------------------------------------------------------------------- 1 | {"exponent":-3,"price":0,"conf":0,"status":"unknown"} 2 | -------------------------------------------------------------------------------- /program/rust/test_data/aggregation/1.result: -------------------------------------------------------------------------------- 1 | {"exponent":-3,"price":10000,"conf":1000,"status":"trading"} 2 | -------------------------------------------------------------------------------- /program/rust/test_data/aggregation/14.result: -------------------------------------------------------------------------------- 1 | {"exponent":-3,"price":11000,"conf":990,"status":"trading"} 2 | -------------------------------------------------------------------------------- /program/rust/test_data/aggregation/18.result: -------------------------------------------------------------------------------- 1 | {"exponent":-3,"price":11000,"conf":250,"status":"trading"} 2 | -------------------------------------------------------------------------------- /program/rust/test_data/aggregation/19.result: -------------------------------------------------------------------------------- 1 | {"exponent":-3,"price":10900,"conf":900,"status":"trading"} 2 | -------------------------------------------------------------------------------- /program/rust/test_data/aggregation/21.result: -------------------------------------------------------------------------------- 1 | {"exponent":-3,"price":11100,"conf":990,"status":"trading"} 2 | -------------------------------------------------------------------------------- /program/rust/test_data/aggregation/23.result: -------------------------------------------------------------------------------- 1 | {"exponent":0,"price":10100,"conf":10,"status":"trading"} 2 | -------------------------------------------------------------------------------- /program/rust/test_data/aggregation/24.result: -------------------------------------------------------------------------------- 1 | {"exponent":0,"price":100010,"conf":990,"status":"trading"} 2 | -------------------------------------------------------------------------------- /program/rust/test_data/aggregation/25.result: -------------------------------------------------------------------------------- 1 | {"exponent":0,"price":100010,"conf":990,"status":"trading"} 2 | -------------------------------------------------------------------------------- /program/rust/test_data/aggregation/26.result: -------------------------------------------------------------------------------- 1 | {"exponent":0,"price":100010,"conf":990,"status":"trading"} 2 | -------------------------------------------------------------------------------- /program/rust/test_data/aggregation/27.result: -------------------------------------------------------------------------------- 1 | {"exponent":0,"price":100500,"conf":500,"status":"trading"} 2 | -------------------------------------------------------------------------------- /program/rust/test_data/aggregation/28.result: -------------------------------------------------------------------------------- 1 | {"exponent":0,"price":100990,"conf":990,"status":"trading"} 2 | -------------------------------------------------------------------------------- /program/rust/test_data/aggregation/29.result: -------------------------------------------------------------------------------- 1 | {"exponent":0,"price":100990,"conf":990,"status":"trading"} 2 | -------------------------------------------------------------------------------- /program/rust/test_data/aggregation/3.result: -------------------------------------------------------------------------------- 1 | {"exponent":-3,"price":0,"conf":1000,"status":"trading"} 2 | -------------------------------------------------------------------------------- /program/rust/test_data/aggregation/30.result: -------------------------------------------------------------------------------- 1 | {"exponent":0,"price":100990,"conf":990,"status":"trading"} 2 | -------------------------------------------------------------------------------- /program/rust/test_data/aggregation/4.result: -------------------------------------------------------------------------------- 1 | {"exponent":-3,"price":10500,"conf":500,"status":"trading"} 2 | -------------------------------------------------------------------------------- /program/rust/test_data/aggregation/5.result: -------------------------------------------------------------------------------- 1 | {"exponent":-3,"price":15000,"conf":4000,"status":"trading"} 2 | -------------------------------------------------------------------------------- /program/rust/test_data/aggregation/6.result: -------------------------------------------------------------------------------- 1 | {"exponent":-3,"price":15000,"conf":4000,"status":"trading"} 2 | -------------------------------------------------------------------------------- /program/rust/test_data/aggregation/8.result: -------------------------------------------------------------------------------- 1 | {"exponent":-3,"price":10020,"conf":970,"status":"trading"} 2 | -------------------------------------------------------------------------------- /program/rust/test_data/aggregation/9.result: -------------------------------------------------------------------------------- 1 | {"exponent":-3,"price":10510,"conf":500,"status":"trading"} 2 | -------------------------------------------------------------------------------- /program/rust/test_data/aggregation/12.result: -------------------------------------------------------------------------------- 1 | {"exponent":-3,"price":10010510,"conf":500,"status":"trading"} 2 | -------------------------------------------------------------------------------- /program/rust/test_data/aggregation/13.result: -------------------------------------------------------------------------------- 1 | {"exponent":-3,"price":13005,"conf":1985,"status":"trading"} 2 | -------------------------------------------------------------------------------- /program/rust/test_data/aggregation/15.result: -------------------------------------------------------------------------------- 1 | {"exponent":-3,"price":13000,"conf":2000,"status":"trading"} 2 | -------------------------------------------------------------------------------- /program/rust/test_data/aggregation/16.result: -------------------------------------------------------------------------------- 1 | {"exponent":-3,"price":13153,"conf":2053,"status":"trading"} 2 | -------------------------------------------------------------------------------- /program/rust/test_data/aggregation/17.result: -------------------------------------------------------------------------------- 1 | {"exponent":-3,"price":13153,"conf":2053,"status":"trading"} 2 | -------------------------------------------------------------------------------- /program/rust/test_data/aggregation/2.result: -------------------------------------------------------------------------------- 1 | {"exponent":-3,"price":-10000,"conf":1000,"status":"trading"} 2 | -------------------------------------------------------------------------------- /program/rust/test_data/aggregation/20.result: -------------------------------------------------------------------------------- 1 | {"exponent":-3,"price":11000,"conf":2000,"status":"trading"} 2 | -------------------------------------------------------------------------------- /program/rust/test_data/aggregation/22.result: -------------------------------------------------------------------------------- 1 | {"exponent":-3,"price":10001000,"conf":4000,"status":"trading"} 2 | -------------------------------------------------------------------------------- /program/rust/test_data/aggregation/7.result: -------------------------------------------------------------------------------- 1 | {"exponent":-3,"price":100010,"conf":899980,"status":"trading"} 2 | -------------------------------------------------------------------------------- /rust-toolchain: -------------------------------------------------------------------------------- 1 | 2 | # This is only used for tests 3 | [toolchain] 4 | channel = "1.66.1" 5 | profile = "minimal" -------------------------------------------------------------------------------- /program/rust/test_data/aggregation/10.result: -------------------------------------------------------------------------------- 1 | {"exponent":-3,"price":501000,"conf":499989499000,"status":"trading"} 2 | -------------------------------------------------------------------------------- /program/rust/test_data/aggregation/39.result: -------------------------------------------------------------------------------- 1 | {"exponent":-8,"price":115000000,"conf":94900000,"status":"trading"} 2 | -------------------------------------------------------------------------------- /program/rust/test_data/aggregation/31.result: -------------------------------------------------------------------------------- 1 | {"exponent":-8,"price":4328634500000,"conf":2914500000,"status":"trading"} 2 | -------------------------------------------------------------------------------- /program/rust/test_data/aggregation/32.result: -------------------------------------------------------------------------------- 1 | {"exponent":-8,"price":4329187000000,"conf":3187000000,"status":"trading"} 2 | -------------------------------------------------------------------------------- /program/rust/test_data/aggregation/33.result: -------------------------------------------------------------------------------- 1 | {"exponent":-8,"price":4329187000000,"conf":3187000000,"status":"trading"} 2 | -------------------------------------------------------------------------------- /program/rust/test_data/aggregation/34.result: -------------------------------------------------------------------------------- 1 | {"exponent":-8,"price":4290170000000,"conf":670275519,"status":"trading"} 2 | -------------------------------------------------------------------------------- /program/rust/test_data/aggregation/36.result: -------------------------------------------------------------------------------- 1 | {"exponent":-8,"price":1002000000,"conf":48998000000,"status":"trading"} 2 | -------------------------------------------------------------------------------- /program/rust/test_data/aggregation/11.result: -------------------------------------------------------------------------------- 1 | {"exponent":-3,"price":500000000000,"conf":499999499990,"status":"trading"} 2 | -------------------------------------------------------------------------------- /program/rust/test_data/aggregation/35.result: -------------------------------------------------------------------------------- 1 | {"exponent":-8,"price":3999400000000,"conf":3959390000000,"status":"trading"} 2 | -------------------------------------------------------------------------------- /program/rust/test_data/aggregation/37.result: -------------------------------------------------------------------------------- 1 | {"exponent":-8,"price":4001000000000,"conf":395998999990000,"status":"trading"} 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | build 3 | debug 4 | target 5 | cmake-build-* 6 | /venv 7 | 8 | # IntelliJ / CLion configuration 9 | .idea 10 | *.iml 11 | 12 | # CMake files 13 | features.h -------------------------------------------------------------------------------- /program/rust/src/bindings.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define static_assert _Static_assert 4 | 5 | #include 6 | #include "../../c/src/oracle/oracle.h" 7 | 8 | const size_t PC_PRICE_T_COMP_OFFSET = offsetof(struct pc_price, comp_); 9 | const size_t PC_MAP_TABLE_T_PROD_OFFSET = offsetof(struct pc_map_table, prod_); 10 | -------------------------------------------------------------------------------- /program/rust/test_data/aggregation/3.json: -------------------------------------------------------------------------------- 1 | { 2 | "exponent": -3, 3 | "quotes": [ 4 | { 5 | "price": 0, 6 | "conf": 1000, 7 | "status": 1 8 | }, 9 | { 10 | "price": 0, 11 | "conf": 1000, 12 | "status": 1 13 | }, 14 | { 15 | "price": 0, 16 | "conf": 1000, 17 | "status": 1 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /program/rust/test_data/aggregation/8.json: -------------------------------------------------------------------------------- 1 | { 2 | "exponent": -3, 3 | "quotes": [ 4 | { 5 | "price": 10000, 6 | "conf": 10, 7 | "status": 1 8 | }, 9 | { 10 | "price": 10020, 11 | "conf": 10, 12 | "status": 1 13 | }, 14 | { 15 | "price": 11000, 16 | "conf": 10, 17 | "status": 1 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /program/rust/test_data/aggregation/1.json: -------------------------------------------------------------------------------- 1 | { 2 | "exponent": -3, 3 | "quotes": [ 4 | { 5 | "price": 10000, 6 | "conf": 1000, 7 | "status": 1 8 | }, 9 | { 10 | "price": 10000, 11 | "conf": 1000, 12 | "status": 1 13 | }, 14 | { 15 | "price": 10000, 16 | "conf": 1000, 17 | "status": 1 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /program/rust/test_data/aggregation/18.json: -------------------------------------------------------------------------------- 1 | { 2 | "exponent": -3, 3 | "quotes": [ 4 | { 5 | "price": 10000, 6 | "conf": 1000, 7 | "status": 1 8 | }, 9 | { 10 | "price": 11000, 11 | "conf": 200, 12 | "status": 1 13 | }, 14 | { 15 | "price": 11100, 16 | "conf": 350, 17 | "status": 1 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /program/rust/test_data/aggregation/4.json: -------------------------------------------------------------------------------- 1 | { 2 | "exponent": -3, 3 | "quotes": [ 4 | { 5 | "price": 10000, 6 | "conf": 1000, 7 | "status": 1 8 | }, 9 | { 10 | "price": 11000, 11 | "conf": 1000, 12 | "status": 1 13 | }, 14 | { 15 | "price": 10500, 16 | "conf": 1000, 17 | "status": 1 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /program/rust/test_data/aggregation/5.json: -------------------------------------------------------------------------------- 1 | { 2 | "exponent": -3, 3 | "quotes": [ 4 | { 5 | "price": 10000, 6 | "conf": 1000, 7 | "status": 1 8 | }, 9 | { 10 | "price": 15000, 11 | "conf": 1000, 12 | "status": 1 13 | }, 14 | { 15 | "price": 20000, 16 | "conf": 1000, 17 | "status": 1 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /program/rust/test_data/aggregation/6.json: -------------------------------------------------------------------------------- 1 | { 2 | "exponent": -3, 3 | "quotes": [ 4 | { 5 | "price": 20000, 6 | "conf": 1000, 7 | "status": 1 8 | }, 9 | { 10 | "price": 10000, 11 | "conf": 1000, 12 | "status": 1 13 | }, 14 | { 15 | "price": 15000, 16 | "conf": 1000, 17 | "status": 1 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /program/rust/test_data/aggregation/7.json: -------------------------------------------------------------------------------- 1 | { 2 | "exponent": -3, 3 | "quotes": [ 4 | { 5 | "price": 100000, 6 | "conf": 10, 7 | "status": 1 8 | }, 9 | { 10 | "price": 100000, 11 | "conf": 10, 12 | "status": 1 13 | }, 14 | { 15 | "price": 1000000, 16 | "conf": 10, 17 | "status": 1 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /program/rust/test_data/aggregation/2.json: -------------------------------------------------------------------------------- 1 | { 2 | "exponent": -3, 3 | "quotes": [ 4 | { 5 | "price": -10000, 6 | "conf": 1000, 7 | "status": 1 8 | }, 9 | { 10 | "price": -10000, 11 | "conf": 1000, 12 | "status": 1 13 | }, 14 | { 15 | "price": -10000, 16 | "conf": 1000, 17 | "status": 1 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /program/rust/test_data/aggregation/10.json: -------------------------------------------------------------------------------- 1 | { 2 | "exponent": -3, 3 | "quotes": [ 4 | { 5 | "price": 500000, 6 | "conf": 10, 7 | "status": 1 8 | }, 9 | { 10 | "price": 501000, 11 | "conf": 20, 12 | "status": 1 13 | }, 14 | { 15 | "price": 500000000000, 16 | "conf": 10000000, 17 | "status": 1 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /program/rust/test_data/aggregation/22.json: -------------------------------------------------------------------------------- 1 | { 2 | "exponent": -3, 3 | "quotes": [ 4 | { 5 | "price": 10000000, 6 | "conf": 5000, 7 | "status": 1 8 | }, 9 | { 10 | "price": 10010000, 11 | "conf": 5000, 12 | "status": 1 13 | }, 14 | { 15 | "price": 10000000, 16 | "conf": 1000, 17 | "status": 1 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /program/rust/test_data/ema/small_conf.csv: -------------------------------------------------------------------------------- 1 | price,conf,expo,nslots 2 | 16531211500,3551850,-8,0 3 | 16531199600,3664650,-8,1 4 | 16530429200,13766600,-8,1 5 | 16533446000,5589400,-8,3 6 | 16533056100,2,-8,2 7 | 16498650000,2,-8,175 8 | 16484437900,2957400,-8,2 9 | 16485951200,15123300,-8,2 10 | 16482523300,10602450,-8,4 11 | 16482958000,17245100,-8,1 12 | -------------------------------------------------------------------------------- /program/rust/test_data/aggregation/11.json: -------------------------------------------------------------------------------- 1 | { 2 | "exponent": -3, 3 | "quotes": [ 4 | { 5 | "price": 500000, 6 | "conf": 10, 7 | "status": 1 8 | }, 9 | { 10 | "price": 501000000000, 11 | "conf": 10000000, 12 | "status": 1 13 | }, 14 | { 15 | "price": 500000000000, 16 | "conf": 20000000, 17 | "status": 1 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /program/c/src/oracle/util/util.h: -------------------------------------------------------------------------------- 1 | #ifndef _pyth_oracle_util_util_h_ 2 | #define _pyth_oracle_util_util_h_ 3 | 4 | #include "align.h" /* includes stdint.h */ 5 | //#include "sar.h" /* includes stdint.h */ 6 | //#include "hash.h" /* includes stdint.h */ 7 | #include "avg.h" /* includes sar.h */ 8 | #include "prng.h" /* includes stdalign.h and hash.h */ 9 | 10 | #endif /* _pyth_oracle_util_util_h_ */ 11 | -------------------------------------------------------------------------------- /program/rust/test_data/aggregation/39.json: -------------------------------------------------------------------------------- 1 | { 2 | "comment": "1 good quote, 1 off by factor of 10 - this is the low quote number, tightest outlier limit.", 3 | "exponent": -8, 4 | "quotes": [ 5 | { 6 | "price": 21000000, 7 | "conf": 100000, 8 | "status": 1 9 | }, 10 | { 11 | "price": 209900000, 12 | "conf": 1000000, 13 | "status": 1 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /program/rust/test_data/aggregation/9.json: -------------------------------------------------------------------------------- 1 | { 2 | "exponent": -3, 3 | "quotes": [ 4 | { 5 | "price": 10000, 6 | "conf": 10, 7 | "status": 1 8 | }, 9 | { 10 | "price": 10020, 11 | "conf": 10, 12 | "status": 1 13 | }, 14 | { 15 | "price": 11000, 16 | "conf": 10, 17 | "status": 1 18 | }, 19 | { 20 | "price": 11020, 21 | "conf": 10, 22 | "status": 1 23 | } 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /program/rust/test_data/aggregation/19.json: -------------------------------------------------------------------------------- 1 | { 2 | "exponent": -3, 3 | "quotes": [ 4 | { 5 | "price": 10000, 6 | "conf": 1000, 7 | "status": 1 8 | }, 9 | { 10 | "price": 11000, 11 | "conf": 200, 12 | "status": 1 13 | }, 14 | { 15 | "price": 11100, 16 | "conf": 350, 17 | "status": 1 18 | }, 19 | { 20 | "price": 8000, 21 | "conf": 5000, 22 | "status": 1 23 | } 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /program/rust/test_data/aggregation/12.json: -------------------------------------------------------------------------------- 1 | { 2 | "exponent": -3, 3 | "quotes": [ 4 | { 5 | "price": 10010000, 6 | "conf": 10, 7 | "status": 1 8 | }, 9 | { 10 | "price": 10010020, 11 | "conf": 10, 12 | "status": 1 13 | }, 14 | { 15 | "price": 10011000, 16 | "conf": 10, 17 | "status": 1 18 | }, 19 | { 20 | "price": 10011020, 21 | "conf": 10, 22 | "status": 1 23 | } 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /program/rust/test_data/aggregation/38.json: -------------------------------------------------------------------------------- 1 | { 2 | "comment": "zero confidence quotes are rejected. With no valid quotes result should be status unknown", 3 | "exponent": -3, 4 | "quotes": [ 5 | { 6 | "price": 10000, 7 | "conf": 0, 8 | "status": 1 9 | }, 10 | { 11 | "price": 10000, 12 | "conf": 0, 13 | "status": 1 14 | }, 15 | { 16 | "price": 10000, 17 | "conf": 0, 18 | "status": 1 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /program/c/src/oracle/upd_aggregate.c: -------------------------------------------------------------------------------- 1 | /* 2 | * BPF upd_aggregate binding 3 | */ 4 | #include 5 | #include "oracle.h" 6 | #include "upd_aggregate.h" 7 | #include "features.h" 8 | 9 | 10 | extern bool c_upd_aggregate_pythnet( pc_price_t *ptr, uint64_t slot, int64_t timestamp ){ 11 | return upd_aggregate(ptr, slot, timestamp ); 12 | } 13 | 14 | extern void c_upd_twap( pc_price_t *ptr, int64_t nslots ){ 15 | upd_twap(ptr, nslots); 16 | } 17 | -------------------------------------------------------------------------------- /scripts/patch-solana.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Patch BPF makefile and solana_sdk.h: 4 | # - Build with `-Wall -Wextra -Wconversion`. 5 | # - Link with `-z defs`. 6 | # - Add TEST_FLAGS for criterion CLI. 7 | # 8 | 9 | set -eu 10 | 11 | THIS_DIR="$( dirname "${BASH_SOURCE[0]}" )" 12 | THIS_DIR="$( cd "${THIS_DIR}" && pwd )" 13 | SOLANA="$(dirname $(which cargo-build-bpf))" 14 | 15 | set -x 16 | cd "${SOLANA}" 17 | patch -p1 -i "${THIS_DIR}/solana.patch" 18 | -------------------------------------------------------------------------------- /.github/workflows/check-fomatting.yml: -------------------------------------------------------------------------------- 1 | name: Check Formatting 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: [main] 7 | 8 | jobs: 9 | pre-commit: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | - uses: actions/setup-python@v2 14 | - uses: actions-rs/toolchain@v1 15 | with: 16 | profile: minimal 17 | toolchain: nightly-2023-03-01 18 | components: rustfmt, clippy 19 | - uses: pre-commit/action@v3.0.0 20 | -------------------------------------------------------------------------------- /program/rust/test_data/aggregation/14.json: -------------------------------------------------------------------------------- 1 | { 2 | "exponent": -3, 3 | "quotes": [ 4 | { 5 | "price": 10000, 6 | "conf": 10, 7 | "status": 1 8 | }, 9 | { 10 | "price": 10010, 11 | "conf": 10, 12 | "status": 1 13 | }, 14 | { 15 | "price": 11000, 16 | "conf": 10, 17 | "status": 1 18 | }, 19 | { 20 | "price": 11010, 21 | "conf": 10, 22 | "status": 1 23 | }, 24 | { 25 | "price": 11500, 26 | "conf": 100, 27 | "status": 1 28 | } 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /program/rust/test_data/aggregation/20.json: -------------------------------------------------------------------------------- 1 | { 2 | "exponent": -3, 3 | "quotes": [ 4 | { 5 | "price": 10000, 6 | "conf": 1000, 7 | "status": 1 8 | }, 9 | { 10 | "price": 11000, 11 | "conf": 200, 12 | "status": 1 13 | }, 14 | { 15 | "price": 11100, 16 | "conf": 350, 17 | "status": 1 18 | }, 19 | { 20 | "price": 8000, 21 | "conf": 5000, 22 | "status": 1 23 | }, 24 | { 25 | "price": 12000, 26 | "conf": 4000, 27 | "status": 1 28 | } 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /program/c/src/oracle/util/test_round.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int 4 | test_round() { 5 | unsigned i = (unsigned)0; 6 | 7 | for( int x=-32767; x<=32767; x++ ) { 8 | for( int y=-32767; y<=32767; y++ ) { 9 | 10 | int u = (x+y)>>1; 11 | int v = (x>>1)+(y>>1); 12 | int w = v + (x&y&1); 13 | if( u!=w ) { 14 | printf( "FAIL (x %3i y %3i u %3i v %3i w %3i)\n", x, y, u, v, w ); 15 | return 1; 16 | } 17 | 18 | i++; 19 | } 20 | } 21 | 22 | return 0; 23 | } 24 | -------------------------------------------------------------------------------- /program/rust/test_data/ema/small_conf.result: -------------------------------------------------------------------------------- 1 | price,conf,expo,nslots,twap,twac 2 | 16531211500,3551850,-8,0,16531211400,3551849 3 | 16531199600,3664650,-8,1,16531205700,3607371 4 | 16530429200,13766600,-8,1,16531115900,4784415 5 | 16533446000,5589400,-8,3,16531633400,4963176 6 | 16533056100,2,-8,2,16533056100,9 7 | 16498650000,2,-8,175,16515675000,5 8 | 16484437900,2957400,-8,2,16515675100,6 9 | 16485951200,15123300,-8,2,16515675000,7 10 | 16482523300,10602450,-8,4,16515675000,8 11 | 16482958000,17245100,-8,1,16515675200,10 12 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | # Merge all imports into a clean vertical list of module imports. 2 | imports_granularity = "One" 3 | group_imports = "One" 4 | imports_layout = "Vertical" 5 | 6 | # Better grep-ability. 7 | empty_item_single_line = false 8 | 9 | # Consistent pipe layout. 10 | match_arm_leading_pipes = "Preserve" 11 | 12 | # Align Fields 13 | enum_discrim_align_threshold = 80 14 | struct_field_align_threshold = 80 15 | 16 | # Allow up to two blank lines for visual grouping. 17 | blank_lines_upper_bound = 2 18 | -------------------------------------------------------------------------------- /scripts/check-size.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | 4 | # While Solana doesn't support resizing programs, the oracle binary needs to be smaller than 81760 bytes 5 | # (The available space for the oracle program on pythnet is 88429 and mainnet is 81760) 6 | ORACLE_SIZE=$(wc -c ./target/deploy/pyth_oracle.so | awk '{print $1}') 7 | if [ $ORACLE_SIZE -lt ${1} ] 8 | then 9 | echo "Size of pyth_oracle.so is small enough to be deployed, since ${ORACLE_SIZE} is less than ${1}" 10 | else 11 | echo "Size of pyth_oracle.so is too big to be deployed, since ${ORACLE_SIZE} is greater than ${1}" 12 | exit 1 13 | fi -------------------------------------------------------------------------------- /program/rust/test_data/publish/products.json: -------------------------------------------------------------------------------- 1 | { 2 | "BTC": { 3 | "symbol": "BTC/USD", 4 | "asset_type": "Crypto", 5 | "country": "US", 6 | "quote_currency": "USD", 7 | "tenor": "Spot" 8 | }, 9 | "ETH": { 10 | "symbol": "ETH/USD", 11 | "asset_type": "Crypto", 12 | "country": "US", 13 | "quote_currency": "USD", 14 | "tenor": "Spot" 15 | }, 16 | "LTC": { 17 | "symbol": "LTC/USD", 18 | "asset_type": "Crypto", 19 | "country": "US", 20 | "quote_currency": "USD", 21 | "tenor": "Spot" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /dashboard/style.css: -------------------------------------------------------------------------------- 1 | * { 2 | font-family: monospace; 3 | } 4 | body { 5 | background-color: #010514; 6 | } 7 | h2 { 8 | color: #A442CC; 9 | } 10 | th { 11 | text-align: center; 12 | color: #425FDC; 13 | } 14 | td { 15 | text-align: right; 16 | color: cornsilk; 17 | } 18 | tr:hover { 19 | background-color: #010305; 20 | } 21 | th, td { 22 | border: 1px solid #202020; 23 | } 24 | #errmsg { 25 | position: fixed; 26 | bottom: 0; 27 | right: 0; 28 | width: 100%; 29 | text-align: right; 30 | color: red; 31 | } 32 | #notify { 33 | position: fixed; 34 | bottom: 0; 35 | width: 100%; 36 | color: white; 37 | } 38 | -------------------------------------------------------------------------------- /program/rust/test_data/aggregation/16.json: -------------------------------------------------------------------------------- 1 | { 2 | "exponent": -3, 3 | "quotes": [ 4 | { 5 | "price": 10000, 6 | "conf": 1100, 7 | "status": 1 8 | }, 9 | { 10 | "price": 11030, 11 | "conf": 900, 12 | "status": 1 13 | }, 14 | { 15 | "price": 12030, 16 | "conf": 1123, 17 | "status": 1 18 | }, 19 | { 20 | "price": 13200, 21 | "conf": 940, 22 | "status": 1 23 | }, 24 | { 25 | "price": 14020, 26 | "conf": 1070, 27 | "status": 1 28 | }, 29 | { 30 | "price": 15200, 31 | "conf": 1123, 32 | "status": 1 33 | }, 34 | { 35 | "price": 16320, 36 | "conf": 1213, 37 | "status": 1 38 | } 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /program/rust/test_data/aggregation/17.json: -------------------------------------------------------------------------------- 1 | { 2 | "exponent": -3, 3 | "quotes": [ 4 | { 5 | "price": 16320, 6 | "conf": 1213, 7 | "status": 1 8 | }, 9 | { 10 | "price": 15200, 11 | "conf": 1123, 12 | "status": 1 13 | }, 14 | { 15 | "price": 14020, 16 | "conf": 1070, 17 | "status": 1 18 | }, 19 | { 20 | "price": 13200, 21 | "conf": 940, 22 | "status": 1 23 | }, 24 | { 25 | "price": 12030, 26 | "conf": 1123, 27 | "status": 1 28 | }, 29 | { 30 | "price": 11030, 31 | "conf": 900, 32 | "status": 1 33 | }, 34 | { 35 | "price": 10000, 36 | "conf": 1100, 37 | "status": 1 38 | } 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /scripts/start-docker-build-env.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -o errexit -o nounset -o pipefail 3 | command -v shellcheck >/dev/null && shellcheck "$0" 4 | 5 | dir=$(pwd) 6 | export PYTH_REPO=${dir} 7 | echo "Mounting PYTH_REPO: ${PYTH_REPO}" 8 | 9 | # Use the latest oracle image from https://hub.docker.com/r/pythfoundation/pyth-client/tags?name=oracle 10 | LATEST_VERSION="v2.21.0" 11 | export IMAGE=pythfoundation/pyth-client:oracle-${LATEST_VERSION} 12 | echo "Using image: ${IMAGE}" 13 | 14 | docker run -it \ 15 | --mount "type=bind,src=${PYTH_REPO},target=/home/pyth/pyth-client" \ 16 | --platform linux/amd64 \ 17 | $IMAGE \ 18 | /bin/bash -l -------------------------------------------------------------------------------- /program/rust/test_data/aggregation/15.json: -------------------------------------------------------------------------------- 1 | { 2 | "exponent": -3, 3 | "quotes": [ 4 | { 5 | "price": 10000, 6 | "conf": 1000, 7 | "status": 1 8 | }, 9 | { 10 | "price": 11000, 11 | "conf": 1000, 12 | "status": 1 13 | }, 14 | { 15 | "price": 12000, 16 | "conf": 1000, 17 | "status": 1 18 | }, 19 | { 20 | "price": 13000, 21 | "conf": 1000, 22 | "status": 1 23 | }, 24 | { 25 | "price": 14000, 26 | "conf": 1000, 27 | "status": 1 28 | }, 29 | { 30 | "price": 15000, 31 | "conf": 1000, 32 | "status": 1 33 | }, 34 | { 35 | "price": 16000, 36 | "conf": 1000, 37 | "status": 1 38 | } 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v3.2.0 4 | hooks: 5 | - id: trailing-whitespace 6 | files: program/ 7 | - id: end-of-file-fixer 8 | files: program/ 9 | - id: check-added-large-files 10 | - repo: local 11 | hooks: 12 | - id: cargo-fmt 13 | name: Cargo Fmt 14 | language: "rust" 15 | entry: cargo +nightly-2023-03-01 fmt 16 | pass_filenames: false 17 | - id: cargo-clippy-pythnet 18 | name: Cargo Clippy Pythnet 19 | language: "rust" 20 | entry: cargo +nightly-2023-03-01 clippy --tests --features check -- -D warnings 21 | pass_filenames: false 22 | -------------------------------------------------------------------------------- /program/c/src/oracle/native/upd_aggregate.c: -------------------------------------------------------------------------------- 1 | /// The goal of this file is to provide the upd_aggregate function to local rust tests 2 | 3 | /// We need to allocate some heap space for upd_aggregate 4 | /// When compiling for the solana runtime, the heap space is preallocated and PC_HEAP_START is provided by 5 | char heap_start[8192]; 6 | #define PC_HEAP_START (heap_start) 7 | #define static_assert _Static_assert 8 | 9 | #include "../upd_aggregate.h" 10 | #include "../features.h" 11 | 12 | extern bool c_upd_aggregate_pythnet( pc_price_t *ptr, uint64_t slot, int64_t timestamp ){ 13 | return upd_aggregate(ptr, slot, timestamp ); 14 | } 15 | 16 | extern void c_upd_twap( pc_price_t *ptr, int64_t nslots ){ 17 | upd_twap(ptr, nslots); 18 | } 19 | -------------------------------------------------------------------------------- /program/rust/src/c_oracle_header.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_camel_case_types)] 2 | #![allow(non_snake_case)] 3 | // We do not use all the variables in oracle.h, so this helps with the warnings 4 | #![allow(dead_code)] 5 | 6 | // Bindings.rs is generated by build.rs to include things defined in bindings.h 7 | include!("../codegen/bindings.rs"); 8 | 9 | /// If ci > price / PC_MAX_CI_DIVISOR, set publisher status to unknown. 10 | /// (e.g., 20 means ci must be < 5% of price) 11 | pub const MAX_CI_DIVISOR: i64 = 3; 12 | /// Bound on the range of the exponent in price accounts. This number is set such that the 13 | /// PD-based EMA computation does not lose too much precision. 14 | pub const MAX_NUM_DECIMALS: i32 = 12; 15 | pub const PRICE_ACCOUNT_DEFAULT_MIN_PUB: u8 = 20; 16 | -------------------------------------------------------------------------------- /pc/mem_map.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | 6 | namespace pc 7 | { 8 | 9 | // memory-mapped file 10 | class mem_map 11 | { 12 | public: 13 | mem_map(); 14 | ~mem_map(); 15 | 16 | // initialize from file 17 | void set_file( const std::string& ); 18 | std::string get_file() const; 19 | bool remap(); 20 | bool init(); 21 | 22 | // access to file data 23 | const char *data() const; 24 | size_t size() const; 25 | 26 | private: 27 | void close(); 28 | int fd_; 29 | size_t len_; 30 | const char *buf_; 31 | std::string file_; 32 | }; 33 | 34 | inline const char *mem_map::data() const 35 | { 36 | return buf_; 37 | } 38 | 39 | inline size_t mem_map::size() const 40 | { 41 | return len_; 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /scripts/check-docker-pin.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # This script is checks to that all our Docker images are pinned to a specific SHA256 hash 4 | # 5 | # References as to why... 6 | # - https://nickjanetakis.com/blog/docker-tip-18-please-pin-your-docker-image-versions 7 | # - https://snyk.io/blog/10-docker-image-security-best-practices/ (Specifically: USE FIXED TAGS FOR IMMUTABILITY) 8 | # 9 | # Explaination of regex ignore choices 10 | # - We ignore sha256 because it suggests that the image dep is pinned 11 | # - We ignore scratch because it's literally the docker base image 12 | # 13 | git ls-files | grep "Dockerfile*" | xargs grep -s "FROM" | egrep -v 'sha256|scratch' 14 | if [ $? -eq 0 ]; then 15 | echo "[!] Unpinned docker files" >&2 16 | exit 1 17 | else 18 | echo "[+] No unpinned docker files" 19 | fi -------------------------------------------------------------------------------- /program/rust/test_data/aggregation/21.json: -------------------------------------------------------------------------------- 1 | { 2 | "exponent": -3, 3 | "quotes": [ 4 | { 5 | "price": 10000, 6 | "conf": 100, 7 | "status": 1 8 | }, 9 | { 10 | "price": 10100, 11 | "conf": 200, 12 | "status": 1 13 | }, 14 | { 15 | "price": 10110, 16 | "conf": 130, 17 | "status": 1 18 | }, 19 | { 20 | "price": 11000, 21 | "conf": 100, 22 | "status": 1 23 | }, 24 | { 25 | "price": 11100, 26 | "conf": 200, 27 | "status": 1 28 | }, 29 | { 30 | "price": 11110, 31 | "conf": 130, 32 | "status": 1 33 | }, 34 | { 35 | "price": 12000, 36 | "conf": 100, 37 | "status": 1 38 | }, 39 | { 40 | "price": 12100, 41 | "conf": 200, 42 | "status": 1 43 | }, 44 | { 45 | "price": 12110, 46 | "conf": 130, 47 | "status": 1 48 | } 49 | ] 50 | } 51 | -------------------------------------------------------------------------------- /program/rust/test_data/aggregation/36.json: -------------------------------------------------------------------------------- 1 | { 2 | "exponent": -8, 3 | "quotes": [ 4 | { 5 | "price": 1000000000, 6 | "conf": 1000000, 7 | "status": 1 8 | }, 9 | { 10 | "price": 1001000000, 11 | "conf": 1000000, 12 | "status": 1 13 | }, 14 | { 15 | "price": 1002000000, 16 | "conf": 1000000, 17 | "status": 1 18 | }, 19 | { 20 | "price": 50000000000, 21 | "conf": 49000000000, 22 | "status": 1 23 | }, 24 | { 25 | "price": 30000000000, 26 | "conf": 29000000000, 27 | "status": 1 28 | }, 29 | { 30 | "price": 40000000000, 31 | "conf": 39010000000, 32 | "status": 1 33 | }, 34 | { 35 | "price": 101500000000, 36 | "conf": 100510000000, 37 | "status": 1 38 | } 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /program/rust/test_data/aggregation/35.json: -------------------------------------------------------------------------------- 1 | { 2 | "exponent": -8, 3 | "quotes": [ 4 | { 5 | "price": 4000000000000, 6 | "conf": 1000000000, 7 | "status": 1 8 | }, 9 | { 10 | "price": 4000100000000, 11 | "conf": 1000000000, 12 | "status": 1 13 | }, 14 | { 15 | "price": 4000200000000, 16 | "conf": 1000000000, 17 | "status": 1 18 | }, 19 | { 20 | "price": 4000300000000, 21 | "conf": 1000000000, 22 | "status": 1 23 | }, 24 | { 25 | "price": 4000400000000, 26 | "conf": 1000000000, 27 | "status": 1 28 | }, 29 | { 30 | "price": 40000000000, 31 | "conf": 10000000, 32 | "status": 1 33 | }, 34 | { 35 | "price": 40000000000, 36 | "conf": 10000000, 37 | "status": 1 38 | } 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /program/rust/test_data/aggregation/37.json: -------------------------------------------------------------------------------- 1 | { 2 | "exponent": -8, 3 | "quotes": [ 4 | { 5 | "price": 4000000000000, 6 | "conf": 1000000000, 7 | "status": 1 8 | }, 9 | { 10 | "price": 4000100000000, 11 | "conf": 1000000000, 12 | "status": 1 13 | }, 14 | { 15 | "price": 4000200000000, 16 | "conf": 1000000000, 17 | "status": 1 18 | }, 19 | { 20 | "price": 4000300000000, 21 | "conf": 1000000000, 22 | "status": 1 23 | }, 24 | { 25 | "price": 4000400000000, 26 | "conf": 1000000000, 27 | "status": 1 28 | }, 29 | { 30 | "price": 400000000000000, 31 | "conf": 10000, 32 | "status": 1 33 | }, 34 | { 35 | "price": 400000000000000, 36 | "conf": 10000, 37 | "status": 1 38 | } 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /docker/fuzz/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:20.04@sha256:fd92c36d3cb9b1d027c4d2a72c6bf0125da82425fc2ca37c414d4f010180dc19 2 | 3 | ENV DEBIAN_FRONTEND=noninteractive 4 | RUN apt-get update 5 | RUN apt-get install -qq cmake curl git libzstd1 libzstd-dev python3-pytest sudo zlib1g zlib1g-dev libssl-dev clang llvm 6 | 7 | # Grant sudo access to pyth user 8 | RUN echo "pyth ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers 9 | 10 | RUN useradd -m pyth 11 | USER pyth 12 | WORKDIR /home/pyth 13 | 14 | # Install AFL 15 | ENV CC=clang 16 | ENV CXX=clang++ 17 | RUN git clone https://github.com/google/AFL.git --branch v2.57b afl 18 | RUN cd afl && make && cd llvm_mode && make && cd /home/pyth 19 | 20 | # Build everything with the AFL compilers 21 | ENV CC=/home/pyth/afl/afl-clang-fast 22 | ENV CXX=/home/pyth/afl/afl-clang-fast++ 23 | 24 | COPY --chown=pyth:pyth . pyth-client/ 25 | RUN cd pyth-client && mkdir build && cd build && cmake .. && make 26 | -------------------------------------------------------------------------------- /pctest/test_error.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #define PC_TEST_START try{ 9 | 10 | #define PC_TEST_END } catch( pc::test_error& err ) {\ 11 | std::cerr << err.what() << std::endl;\ 12 | _exit( 1 );\ 13 | } catch ( std::exception& err ) {\ 14 | std::cerr << "exception err:" << err.what() << std::endl;\ 15 | _exit( 1 );\ 16 | } 17 | 18 | #define PC_TEST_CHECK(X) \ 19 | if ( !(X) ) { throw pc::test_error( __FILE__, __LINE__); } 20 | 21 | namespace pc 22 | { 23 | class test_error : public std::exception 24 | { 25 | public: 26 | test_error( const std::string& filenm, int line ) 27 | : val_( "test_error: file:" + filenm + 28 | ", line:" + std::to_string( line ) ) { 29 | } 30 | const char *what() const noexcept { 31 | return val_.c_str(); 32 | } 33 | private: 34 | std::string val_; 35 | }; 36 | 37 | } 38 | -------------------------------------------------------------------------------- /program/rust/test_data/aggregation/33.json: -------------------------------------------------------------------------------- 1 | { 2 | "exponent": -8, 3 | "quotes": [ 4 | { 5 | "price": 4329605500000, 6 | "conf": 1486500000, 7 | "status": 1 8 | }, 9 | { 10 | "price": 4325720000000, 11 | "conf": 1290000000, 12 | "status": 1 13 | }, 14 | { 15 | "price": 43254, 16 | "conf": 1, 17 | "status": 1 18 | }, 19 | { 20 | "price": 4329150000000, 21 | "conf": 1500000000, 22 | "status": 1 23 | }, 24 | { 25 | "price": 4329999999999, 26 | "conf": 3999999999, 27 | "status": 1 28 | }, 29 | { 30 | "price": 4331609000000, 31 | "conf": 2422000000, 32 | "status": 1 33 | }, 34 | { 35 | "price": 4329198852938, 36 | "conf": 3044669090, 37 | "status": 1 38 | }, 39 | { 40 | "price": 4327900000000, 41 | "conf": 2000000000, 42 | "status": 1 43 | }, 44 | { 45 | "price": 4332090000000, 46 | "conf": 1000000, 47 | "status": 1 48 | } 49 | ] 50 | } 51 | -------------------------------------------------------------------------------- /program/rust/test_data/aggregation/32.json: -------------------------------------------------------------------------------- 1 | { 2 | "exponent": -8, 3 | "quotes": [ 4 | { 5 | "price": 4329605500000, 6 | "conf": 1486500000, 7 | "status": 1 8 | }, 9 | { 10 | "price": 4325720000000, 11 | "conf": 1290000000, 12 | "status": 1 13 | }, 14 | { 15 | "price": 4329150000000, 16 | "conf": 1500000000, 17 | "status": 1 18 | }, 19 | { 20 | "price": 4329999999999, 21 | "conf": 3999999999, 22 | "status": 1 23 | }, 24 | { 25 | "price": 4331609000000, 26 | "conf": 2422000000, 27 | "status": 1 28 | }, 29 | { 30 | "price": 4329198852938, 31 | "conf": 3044669090, 32 | "status": 1 33 | }, 34 | { 35 | "price": 0, 36 | "conf": 200000000, 37 | "status": 1 38 | }, 39 | { 40 | "price": 4327900000000, 41 | "conf": 2000000000, 42 | "status": 1 43 | }, 44 | { 45 | "price": 4332090000000, 46 | "conf": 1000000, 47 | "status": 1 48 | } 49 | ] 50 | } 51 | -------------------------------------------------------------------------------- /program/rust/src/tests/mod.rs: -------------------------------------------------------------------------------- 1 | mod pyth_simulator; 2 | mod test_add_price; 3 | mod test_add_product; 4 | mod test_add_publisher; 5 | mod test_aggregate_v2; 6 | mod test_aggregation; 7 | mod test_aggregation_zero_conf; 8 | mod test_c_code; 9 | mod test_check_valid_signable_account_or_permissioned_funding_account; 10 | mod test_del_price; 11 | mod test_del_product; 12 | mod test_del_publisher; 13 | mod test_ema; 14 | mod test_full_publisher_set; 15 | mod test_init_mapping; 16 | mod test_init_price; 17 | mod test_message; 18 | mod test_permission_migration; 19 | mod test_publish; 20 | mod test_publish_batch; 21 | mod test_resize_mapping; 22 | mod test_set_max_latency; 23 | mod test_set_min_pub; 24 | mod test_sizes; 25 | mod test_upd_aggregate; 26 | mod test_upd_permissions; 27 | mod test_upd_price; 28 | mod test_upd_price_no_fail_on_error; 29 | mod test_upd_price_with_validator; 30 | mod test_upd_product; 31 | mod test_utils; 32 | 33 | 34 | mod test_twap; 35 | mod test_upd_price_v2; 36 | -------------------------------------------------------------------------------- /program/rust/test_data/aggregation/23.json: -------------------------------------------------------------------------------- 1 | { 2 | "exponent": 0, 3 | "quotes": [ 4 | { 5 | "price": 9900, 6 | "conf": 10, 7 | "status": 1 8 | }, 9 | { 10 | "price": 10100, 11 | "conf": 10, 12 | "status": 1 13 | }, 14 | { 15 | "price": 10100, 16 | "conf": 10, 17 | "status": 1 18 | }, 19 | { 20 | "price": 10100, 21 | "conf": 10, 22 | "status": 1 23 | }, 24 | { 25 | "price": 10100, 26 | "conf": 10, 27 | "status": 1 28 | }, 29 | { 30 | "price": 10100, 31 | "conf": 10, 32 | "status": 1 33 | }, 34 | { 35 | "price": 10100, 36 | "conf": 10, 37 | "status": 1 38 | }, 39 | { 40 | "price": 10100, 41 | "conf": 10, 42 | "status": 1 43 | }, 44 | { 45 | "price": 10100, 46 | "conf": 10, 47 | "status": 1 48 | }, 49 | { 50 | "price": 10100, 51 | "conf": 10, 52 | "status": 1 53 | }, 54 | { 55 | "price": 10100, 56 | "conf": 10, 57 | "status": 1 58 | } 59 | ] 60 | } 61 | -------------------------------------------------------------------------------- /program/rust/test_data/aggregation/24.json: -------------------------------------------------------------------------------- 1 | { 2 | "exponent": 0, 3 | "quotes": [ 4 | { 5 | "price": 100000, 6 | "conf": 10, 7 | "status": 1 8 | }, 9 | { 10 | "price": 100000, 11 | "conf": 10, 12 | "status": 1 13 | }, 14 | { 15 | "price": 100000, 16 | "conf": 10, 17 | "status": 1 18 | }, 19 | { 20 | "price": 100000, 21 | "conf": 10, 22 | "status": 1 23 | }, 24 | { 25 | "price": 100000, 26 | "conf": 10, 27 | "status": 1 28 | }, 29 | { 30 | "price": 101000, 31 | "conf": 10, 32 | "status": 1 33 | }, 34 | { 35 | "price": 101000, 36 | "conf": 10, 37 | "status": 1 38 | }, 39 | { 40 | "price": 101000, 41 | "conf": 10, 42 | "status": 1 43 | }, 44 | { 45 | "price": 101000, 46 | "conf": 10, 47 | "status": 1 48 | }, 49 | { 50 | "price": 101000, 51 | "conf": 10, 52 | "status": 1 53 | }, 54 | { 55 | "price": 90000, 56 | "conf": 1, 57 | "status": 1 58 | } 59 | ] 60 | } 61 | -------------------------------------------------------------------------------- /program/rust/test_data/aggregation/25.json: -------------------------------------------------------------------------------- 1 | { 2 | "exponent": 0, 3 | "quotes": [ 4 | { 5 | "price": 100000, 6 | "conf": 10, 7 | "status": 1 8 | }, 9 | { 10 | "price": 100000, 11 | "conf": 10, 12 | "status": 1 13 | }, 14 | { 15 | "price": 100000, 16 | "conf": 10, 17 | "status": 1 18 | }, 19 | { 20 | "price": 100000, 21 | "conf": 10, 22 | "status": 1 23 | }, 24 | { 25 | "price": 100000, 26 | "conf": 10, 27 | "status": 1 28 | }, 29 | { 30 | "price": 101000, 31 | "conf": 10, 32 | "status": 1 33 | }, 34 | { 35 | "price": 101000, 36 | "conf": 10, 37 | "status": 1 38 | }, 39 | { 40 | "price": 101000, 41 | "conf": 10, 42 | "status": 1 43 | }, 44 | { 45 | "price": 101000, 46 | "conf": 10, 47 | "status": 1 48 | }, 49 | { 50 | "price": 101000, 51 | "conf": 10, 52 | "status": 1 53 | }, 54 | { 55 | "price": 99000, 56 | "conf": 1, 57 | "status": 1 58 | } 59 | ] 60 | } 61 | -------------------------------------------------------------------------------- /program/rust/test_data/aggregation/26.json: -------------------------------------------------------------------------------- 1 | { 2 | "exponent": 0, 3 | "quotes": [ 4 | { 5 | "price": 100000, 6 | "conf": 10, 7 | "status": 1 8 | }, 9 | { 10 | "price": 100000, 11 | "conf": 10, 12 | "status": 1 13 | }, 14 | { 15 | "price": 100000, 16 | "conf": 10, 17 | "status": 1 18 | }, 19 | { 20 | "price": 100000, 21 | "conf": 10, 22 | "status": 1 23 | }, 24 | { 25 | "price": 100000, 26 | "conf": 10, 27 | "status": 1 28 | }, 29 | { 30 | "price": 101000, 31 | "conf": 10, 32 | "status": 1 33 | }, 34 | { 35 | "price": 101000, 36 | "conf": 10, 37 | "status": 1 38 | }, 39 | { 40 | "price": 101000, 41 | "conf": 10, 42 | "status": 1 43 | }, 44 | { 45 | "price": 101000, 46 | "conf": 10, 47 | "status": 1 48 | }, 49 | { 50 | "price": 101000, 51 | "conf": 10, 52 | "status": 1 53 | }, 54 | { 55 | "price": 100000, 56 | "conf": 1, 57 | "status": 1 58 | } 59 | ] 60 | } 61 | -------------------------------------------------------------------------------- /program/rust/test_data/aggregation/27.json: -------------------------------------------------------------------------------- 1 | { 2 | "exponent": 0, 3 | "quotes": [ 4 | { 5 | "price": 100000, 6 | "conf": 10, 7 | "status": 1 8 | }, 9 | { 10 | "price": 100000, 11 | "conf": 10, 12 | "status": 1 13 | }, 14 | { 15 | "price": 100000, 16 | "conf": 10, 17 | "status": 1 18 | }, 19 | { 20 | "price": 100000, 21 | "conf": 10, 22 | "status": 1 23 | }, 24 | { 25 | "price": 100000, 26 | "conf": 10, 27 | "status": 1 28 | }, 29 | { 30 | "price": 101000, 31 | "conf": 10, 32 | "status": 1 33 | }, 34 | { 35 | "price": 101000, 36 | "conf": 10, 37 | "status": 1 38 | }, 39 | { 40 | "price": 101000, 41 | "conf": 10, 42 | "status": 1 43 | }, 44 | { 45 | "price": 101000, 46 | "conf": 10, 47 | "status": 1 48 | }, 49 | { 50 | "price": 101000, 51 | "conf": 10, 52 | "status": 1 53 | }, 54 | { 55 | "price": 100500, 56 | "conf": 1, 57 | "status": 1 58 | } 59 | ] 60 | } 61 | -------------------------------------------------------------------------------- /program/rust/test_data/aggregation/28.json: -------------------------------------------------------------------------------- 1 | { 2 | "exponent": 0, 3 | "quotes": [ 4 | { 5 | "price": 100000, 6 | "conf": 10, 7 | "status": 1 8 | }, 9 | { 10 | "price": 100000, 11 | "conf": 10, 12 | "status": 1 13 | }, 14 | { 15 | "price": 100000, 16 | "conf": 10, 17 | "status": 1 18 | }, 19 | { 20 | "price": 100000, 21 | "conf": 10, 22 | "status": 1 23 | }, 24 | { 25 | "price": 100000, 26 | "conf": 10, 27 | "status": 1 28 | }, 29 | { 30 | "price": 101000, 31 | "conf": 10, 32 | "status": 1 33 | }, 34 | { 35 | "price": 101000, 36 | "conf": 10, 37 | "status": 1 38 | }, 39 | { 40 | "price": 101000, 41 | "conf": 10, 42 | "status": 1 43 | }, 44 | { 45 | "price": 101000, 46 | "conf": 10, 47 | "status": 1 48 | }, 49 | { 50 | "price": 101000, 51 | "conf": 10, 52 | "status": 1 53 | }, 54 | { 55 | "price": 101000, 56 | "conf": 1, 57 | "status": 1 58 | } 59 | ] 60 | } 61 | -------------------------------------------------------------------------------- /program/rust/test_data/aggregation/29.json: -------------------------------------------------------------------------------- 1 | { 2 | "exponent": 0, 3 | "quotes": [ 4 | { 5 | "price": 100000, 6 | "conf": 10, 7 | "status": 1 8 | }, 9 | { 10 | "price": 100000, 11 | "conf": 10, 12 | "status": 1 13 | }, 14 | { 15 | "price": 100000, 16 | "conf": 10, 17 | "status": 1 18 | }, 19 | { 20 | "price": 100000, 21 | "conf": 10, 22 | "status": 1 23 | }, 24 | { 25 | "price": 100000, 26 | "conf": 10, 27 | "status": 1 28 | }, 29 | { 30 | "price": 101000, 31 | "conf": 10, 32 | "status": 1 33 | }, 34 | { 35 | "price": 101000, 36 | "conf": 10, 37 | "status": 1 38 | }, 39 | { 40 | "price": 101000, 41 | "conf": 10, 42 | "status": 1 43 | }, 44 | { 45 | "price": 101000, 46 | "conf": 10, 47 | "status": 1 48 | }, 49 | { 50 | "price": 101000, 51 | "conf": 10, 52 | "status": 1 53 | }, 54 | { 55 | "price": 111000, 56 | "conf": 1, 57 | "status": 1 58 | } 59 | ] 60 | } 61 | -------------------------------------------------------------------------------- /program/rust/test_data/aggregation/30.json: -------------------------------------------------------------------------------- 1 | { 2 | "exponent": 0, 3 | "quotes": [ 4 | { 5 | "price": 100000, 6 | "conf": 10, 7 | "status": 1 8 | }, 9 | { 10 | "price": 100000, 11 | "conf": 10, 12 | "status": 1 13 | }, 14 | { 15 | "price": 100000, 16 | "conf": 10, 17 | "status": 1 18 | }, 19 | { 20 | "price": 100000, 21 | "conf": 10, 22 | "status": 1 23 | }, 24 | { 25 | "price": 100000, 26 | "conf": 10, 27 | "status": 1 28 | }, 29 | { 30 | "price": 101000, 31 | "conf": 10, 32 | "status": 1 33 | }, 34 | { 35 | "price": 101000, 36 | "conf": 10, 37 | "status": 1 38 | }, 39 | { 40 | "price": 101000, 41 | "conf": 10, 42 | "status": 1 43 | }, 44 | { 45 | "price": 101000, 46 | "conf": 10, 47 | "status": 1 48 | }, 49 | { 50 | "price": 101000, 51 | "conf": 10, 52 | "status": 1 53 | }, 54 | { 55 | "price": 1110000, 56 | "conf": 1, 57 | "status": 1 58 | } 59 | ] 60 | } 61 | -------------------------------------------------------------------------------- /dashboard/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | pyth dashboard 5 | 6 | 7 | 8 | 9 |

pyth dashboard 10 |

11 |

12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 |
atypecntsymbolptypepriceconftwaptwacstatusnqtvalid_slotpub_slottenorcurrdescription
31 |
32 |
33 |
34 | 35 | 36 | -------------------------------------------------------------------------------- /program/rust/test_data/ema/price10_conf1.csv: -------------------------------------------------------------------------------- 1 | price,conf,expo,nslots 2 | 1234567890,1,0,5000 3 | 1234567890,1,0,1 4 | 1234567890,1,0,1 5 | 1234567890,1,0,1 6 | 1234567890,1,0,1 7 | 1234567890,1,0,1 8 | 1234567890,1,0,1 9 | 1234567890,1,0,1 10 | 1234567890,1,0,1 11 | 1234567890,1,0,1 12 | 1234567890,1,0,1 13 | 1234567890,1,0,1 14 | 1234567890,1,0,1 15 | 1234567890,1,0,1 16 | 1234567890,1,0,1 17 | 1234567890,1,0,1 18 | 1234567890,1,0,1 19 | 1234567890,1,0,1 20 | 1234567890,1,0,1 21 | 1234567890,1,0,1 22 | 1234567890,1,0,1 23 | 1234567890,1,0,1 24 | 1234567890,1,0,1 25 | 1234567890,1,0,1 26 | 1234567890,1,0,1 27 | 1234567890,1,0,1 28 | 1234567890,1,0,1 29 | 1234567890,1,0,1 30 | 1234567890,1,0,1 31 | 1234567890,1,0,1 32 | 1234567890,1,0,1 33 | 1234567890,1,0,1 34 | 1234567890,1,0,1 35 | 1234567890,1,0,1 36 | 1234567890,1,0,1 37 | 1234567890,1,0,1 38 | 1234567890,1,0,1 39 | 1234567890,1,0,1 40 | 1234567890,1,0,1 41 | 1234567890,1,0,1 42 | 1234567890,1,0,1 43 | 1234567890,1,0,1 44 | 1234567890,1,0,1 45 | 1234567890,1,0,1 46 | -------------------------------------------------------------------------------- /program/rust/test_data/aggregation/31.json: -------------------------------------------------------------------------------- 1 | { 2 | "exponent": -8, 3 | "quotes": [ 4 | { 5 | "price": 4329605500000, 6 | "conf": 1486500000, 7 | "status": 1 8 | }, 9 | { 10 | "price": 4325720000000, 11 | "conf": 1290000000, 12 | "status": 1 13 | }, 14 | { 15 | "price": 43254, 16 | "conf": 1, 17 | "status": 1 18 | }, 19 | { 20 | "price": 4329150000000, 21 | "conf": 1500000000, 22 | "status": 1 23 | }, 24 | { 25 | "price": 4329999999999, 26 | "conf": 3999999999, 27 | "status": 1 28 | }, 29 | { 30 | "price": 4331609000000, 31 | "conf": 2422000000, 32 | "status": 1 33 | }, 34 | { 35 | "price": 4329198852938, 36 | "conf": 3044669090, 37 | "status": 1 38 | }, 39 | { 40 | "price": 0, 41 | "conf": 200000000, 42 | "status": 1 43 | }, 44 | { 45 | "price": 4327900000000, 46 | "conf": 2000000000, 47 | "status": 1 48 | }, 49 | { 50 | "price": 4332090000000, 51 | "conf": 1000000, 52 | "status": 1 53 | } 54 | ] 55 | } 56 | -------------------------------------------------------------------------------- /program/c/src/oracle/util/compat_stdint.h: -------------------------------------------------------------------------------- 1 | #ifndef _pyth_oracle_util_compat_stdint_h_ 2 | #define _pyth_oracle_util_compat_stdint_h_ 3 | 4 | /* Include functionality from . Define 5 | PYTH_ORACLE_UTIL_COMPAT_STDINT_STYLE to indicate how to do this: 6 | 0 - Use stdint.h directly 7 | 1 - Use solana_sdk.h (solana uses its own definitions for stdint 8 | types and that can conflicts with stdint.h) 9 | Defaults to 0 or 1 depending on if __bpf__ is set. */ 10 | 11 | 12 | #ifndef PYTH_ORACLE_UTIL_COMPAT_STDINT_STYLE 13 | #if !defined(__bpf__) && !defined(SOL_TEST) 14 | #define PYTH_ORACLE_UTIL_COMPAT_STDINT_STYLE 0 15 | #else 16 | #define PYTH_ORACLE_UTIL_COMPAT_STDINT_STYLE 1 17 | #endif 18 | #endif 19 | 20 | 21 | #if PYTH_ORACLE_UTIL_COMPAT_STDINT_STYLE==0 22 | #include 23 | #elif PYTH_ORACLE_UTIL_COMPAT_STDINT_STYLE==1 24 | #include 25 | typedef uint64_t uintptr_t; 26 | #else 27 | #error "Unsupported PYTH_ORACLE_UTIL_COMPAT_STDINT_STYLE" 28 | #endif 29 | 30 | #endif /* _pyth_oracle_util_compat_stdint_h_ */ 31 | -------------------------------------------------------------------------------- /scripts/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # release build: 4 | # ~/pyth-client$ ./scripts/build.sh [build] 5 | # 6 | # debug build: 7 | # ~/pyth-client$ ./scripts/build.sh debug 8 | # 9 | # build other project: 10 | # ~/pyth-client$ ./scripts/build.sh ../serum-pyth/build 11 | # ~/serum-pyth$ ../pyth-client/scripts/build.sh 12 | # 13 | 14 | set -eu 15 | 16 | if [[ $# -ge 1 ]] 17 | then 18 | BUILD_DIR="$1" 19 | shift 20 | else 21 | BUILD_DIR="build" 22 | fi 23 | 24 | ROOT_DIR="$( mkdir -p "${BUILD_DIR}" && cd "${BUILD_DIR}/.." && pwd )" 25 | BUILD_DIR="$( cd "${BUILD_DIR}" && pwd )" 26 | BUILD_DIR="$( basename "${BUILD_DIR}" )" 27 | 28 | if [[ ! -v CMAKE_ARGS && $BUILD_DIR == *debug* ]] 29 | then 30 | CMAKE_ARGS=( "-DCMAKE_BUILD_TYPE=Debug" ) 31 | fi 32 | 33 | # Check before rm -rf $BUILD_DIR 34 | if [[ ! -f "${ROOT_DIR}/CMakeLists.txt" ]] 35 | then 36 | >&2 echo "Not a cmake project dir: ${ROOT_DIR}" 37 | exit 1 38 | fi 39 | 40 | set -x 41 | 42 | cd "${ROOT_DIR}" 43 | rm -rf "${BUILD_DIR}" 44 | mkdir "${BUILD_DIR}" 45 | 46 | cd "${BUILD_DIR}" 47 | cmake ${CMAKE_ARGS[@]+"${CMAKE_ARGS[@]}"} .. 48 | make "$@" 49 | ctest 50 | -------------------------------------------------------------------------------- /doc/cpp-batching-upgrade.md: -------------------------------------------------------------------------------- 1 | # Batch Price Updates 2 | 3 | As of 2.10.1, `pyth-client` has support for sending batched price updates. This significantly reduces SOL burn, so we strongly advise that you update to this newer version. 4 | 5 | ## Websocket API 6 | If you are using the [websocket JRPC API](websocket_api.md) to send updates to `pythd`, all you need to do is update the version of pythd you are running to [`2.10.1`](https://github.com/pyth-network/pyth-client/releases/tag/mainnet-v2.10.1). 7 | 8 | ## C++ Bindings 9 | If you are linking against the C++ bindings, you will need to make some changes to your code to support sending batched updates. 10 | 11 | Instead of sending individual price updates in response to `price_sched` callbacks, you will now need to call `pc::price::send` with a vector of `pc::price` objects that you wish to publish, in response to `on_slot_publish` callbacks. These will then be batched into transactions. The [`test_publish.cpp`](../pctest/test_publish.cpp) example has been updated to use this new approach. 12 | 13 | **Important**: this is a breaking change for C++ publishers, so you will need to update your code as described above for `2.10.1` to work. 14 | -------------------------------------------------------------------------------- /pctest/test_products.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "account": "F4ApWEDXUrWgrHsV9u7msKdgTLUBBSfVynWfAuMr5pYi", 4 | "attr_dict": { 5 | "symbol": "SYMBOL1/USD", 6 | "asset_type": "Equity", 7 | "country": "USA", 8 | "description": "pyth example product #1", 9 | "quote_currency": "USD", 10 | "tenor": "Spot", 11 | "cms_symbol": "SYMBOL1", 12 | "cqs_symbol": "SYMBOL1", 13 | "nasdaq_symbol": "SYMBOL1" 14 | }, 15 | "price_accounts": [ 16 | { 17 | "price_exponent": -4, 18 | "price_type": "price", 19 | "account": "8u5gXUeZcz1QbydwRfgEgF4AphsifFnvFvVZbG1qAiiU" 20 | } 21 | ] 22 | }, 23 | { 24 | "account": "3H1pzmPV6BnfYyjSoMwje47wW735UDseqdxQRo7rQ6uL", 25 | "attr_dict": { 26 | "symbol": "SYMBOL2/USD", 27 | "asset_type": "Equity", 28 | "country": "USA", 29 | "description": "pyth example product #2", 30 | "quote_currency": "USD", 31 | "tenor": "Spot", 32 | "cms_symbol": "SYMBOL2", 33 | "cqs_symbol": "SYMBOL2", 34 | "nasdaq_symbol": "SYMBOL2" 35 | }, 36 | "price_accounts": [ 37 | { 38 | "price_exponent": -6, 39 | "price_type": "price", 40 | "account": "K9yykYWpDnXnt6f36G6dQFKitqxgbESauC8MpFrUyhx" 41 | } 42 | ] 43 | } 44 | ] -------------------------------------------------------------------------------- /program/rust/src/accounts/mapping.rs: -------------------------------------------------------------------------------- 1 | use { 2 | super::{ 3 | AccountHeader, 4 | PythAccount, 5 | }, 6 | crate::c_oracle_header::{ 7 | PC_ACCTYPE_MAPPING, 8 | PC_MAP_TABLE_SIZE, 9 | PC_MAP_TABLE_T_PROD_OFFSET, 10 | }, 11 | bytemuck::{ 12 | Pod, 13 | Zeroable, 14 | }, 15 | solana_program::pubkey::Pubkey, 16 | }; 17 | 18 | #[repr(C)] 19 | #[derive(Copy, Clone)] 20 | pub struct MappingAccount { 21 | pub header: AccountHeader, 22 | pub number_of_products: u32, 23 | pub unused_: u32, 24 | pub next_mapping_account: Pubkey, 25 | pub products_list: [Pubkey; PC_MAP_TABLE_SIZE as usize], 26 | } 27 | 28 | impl PythAccount for MappingAccount { 29 | const ACCOUNT_TYPE: u32 = PC_ACCTYPE_MAPPING; 30 | /// Equal to the offset of `prod_` in `MappingAccount`, see the trait comment for more detail 31 | const INITIAL_SIZE: u32 = PC_MAP_TABLE_T_PROD_OFFSET as u32; 32 | } 33 | 34 | // Unsafe impl because product_list is of size 640 and there's no derived trait for this size 35 | unsafe impl Pod for MappingAccount { 36 | } 37 | 38 | unsafe impl Zeroable for MappingAccount { 39 | } 40 | -------------------------------------------------------------------------------- /program/rust/test_data/aggregation/13.json: -------------------------------------------------------------------------------- 1 | { 2 | "exponent": -3, 3 | "quotes": [ 4 | { 5 | "price": 10000, 6 | "conf": 10, 7 | "status": 1 8 | }, 9 | { 10 | "price": 10020, 11 | "conf": 100, 12 | "status": 1 13 | }, 14 | { 15 | "price": 11000, 16 | "conf": 10, 17 | "status": 1 18 | }, 19 | { 20 | "price": 11020, 21 | "conf": 100, 22 | "status": 1 23 | }, 24 | { 25 | "price": 12000, 26 | "conf": 10, 27 | "status": 1 28 | }, 29 | { 30 | "price": 12020, 31 | "conf": 100, 32 | "status": 1 33 | }, 34 | { 35 | "price": 13000, 36 | "conf": 10, 37 | "status": 1 38 | }, 39 | { 40 | "price": 13020, 41 | "conf": 100, 42 | "status": 1 43 | }, 44 | { 45 | "price": 14000, 46 | "conf": 10, 47 | "status": 1 48 | }, 49 | { 50 | "price": 14020, 51 | "conf": 100, 52 | "status": 1 53 | }, 54 | { 55 | "price": 15000, 56 | "conf": 10, 57 | "status": 1 58 | }, 59 | { 60 | "price": 15020, 61 | "conf": 100, 62 | "status": 1 63 | }, 64 | { 65 | "price": 16000, 66 | "conf": 10, 67 | "status": 1 68 | }, 69 | { 70 | "price": 16020, 71 | "conf": 100, 72 | "status": 1 73 | } 74 | ] 75 | } 76 | -------------------------------------------------------------------------------- /pctest/init_key_store.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | check() 4 | { 5 | CMD=$1 6 | echo "$CMD" 7 | eval $CMD 8 | RC=$? 9 | if [ $RC -ne 0 ]; then 10 | echo "unexpected error executing rc= $RC" 11 | exit 1 12 | fi 13 | } 14 | 15 | KENV=$1 16 | KDIR=$2 17 | case $KENV in 18 | mainnet) 19 | MAP_KEY=AHtgzX45WTKfkPG53L6WYhGEXwQkN1BVknET3sVsLL8J 20 | PGM_KEY=FsJ3A3u2vn5cTVofAjvy6y5kwABJAqYWpe4975bi2epH 21 | ;; 22 | devnet) 23 | MAP_KEY=BmA9Z6FjioHJPpjT39QazZyhDRUdZy2ezwx4GiDdE2u2 24 | PGM_KEY=gSbePebfvPy7tRqimPoVecS2UsBvYv46ynrzWocc92s 25 | ;; 26 | testnet) 27 | MAP_KEY=AFmdnt9ng1uVxqCmqwQJDAYC5cKTkw8gJKSM5PnzuF6z 28 | PGM_KEY=8tfDNiaEyrV6Q1U4DEXrEigs9DoDtkugzFbybENEbCDz 29 | ;; 30 | *) 31 | echo "Unknown environment. Please use: mainnet, devnet, testnet" 32 | exit 1; 33 | esac 34 | if [ -z "$KDIR" ] ; then 35 | KDIR=$HOME/.pythd 36 | fi 37 | 38 | PKEY_FILE=$KDIR/publish_key_pair.json 39 | if [ ! -f $PKEY_FILE ] ; then 40 | echo "cannot find $PKEY_FILE" 41 | exit 1 42 | fi 43 | 44 | echo $PGM_KEY > $KDIR/program_key.json 45 | check "chmod 0400 $KDIR/program_key.json" 46 | echo $MAP_KEY > $KDIR/mapping_key.json 47 | check "chmod 0400 $KDIR/mapping_key.json" 48 | check "chmod 0700 $KDIR" 49 | -------------------------------------------------------------------------------- /scripts/solana.patch: -------------------------------------------------------------------------------- 1 | diff --git a/sdk/bpf/c/bpf.mk b/sdk/bpf/c/bpf.mk 2 | index 541629ad49..8c2ec94041 100644 3 | --- a/sdk/bpf/c/bpf.mk 4 | +++ b/sdk/bpf/c/bpf.mk 5 | @@ -14,6 +14,12 @@ TEST_PREFIX ?= test_ 6 | OUT_DIR ?= ./out 7 | OS := $(shell uname) 8 | 9 | +ifeq ($(V),1) 10 | +TEST_FLAGS ?= --verbose 11 | +else 12 | +TEST_FLAGS ?= 13 | +endif 14 | + 15 | LLVM_DIR = $(LOCAL_PATH)../dependencies/bpf-tools/llvm 16 | LLVM_SYSTEM_INC_DIRS := $(LLVM_DIR)/lib/clang/14.0.0/include 17 | COMPILER_RT_DIR = $(LOCAL_PATH)../dependencies/bpf-tools/rust/lib/rustlib/bpfel-unknown-unknown/lib 18 | @@ -33,8 +39,11 @@ SYSTEM_INC_DIRS := \ 19 | $(LLVM_SYSTEM_INC_DIRS) \ 20 | 21 | C_FLAGS := \ 22 | + -Wall \ 23 | + -Wextra \ 24 | + -Wconversion \ 25 | -Werror \ 26 | - -O2 \ 27 | + -Oz \ 28 | -fno-builtin \ 29 | -std=c17 \ 30 | $(addprefix -isystem,$(SYSTEM_INC_DIRS)) \ 31 | @@ -68,6 +77,7 @@ BPF_CXX_FLAGS := \ 32 | -march=bpfel+solana 33 | 34 | BPF_LLD_FLAGS := \ 35 | + -z defs \ 36 | -z notext \ 37 | -shared \ 38 | --Bdynamic \ 39 | @@ -245,6 +255,7 @@ define TEST_EXEC_RULE 40 | $1: $2 41 | LD_LIBRARY_PATH=$(TESTFRAMEWORK_RPATH) \ 42 | $2$(\n) 43 | + $2 $(TEST_FLAGS)$(\n) 44 | endef 45 | 46 | .PHONY: $(INSTALL_SH) 47 | -------------------------------------------------------------------------------- /scripts/get-bpf-tools.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # The goal of this script is downloading our own version of bpf/sbf tools. When cargo-build-bpf/sbf gets called 4 | # https://github.com/solana-labs/sbf-tools/releases/download/v1.29/solana-bpf-tools-linux.tar.bz2 gets downloaded, extracted and linked as a toolchain. 5 | # This script is meant to override that behavior and use another version of bpf/sbf-tools. 6 | # Using a custom version of bpf/sbf-tools allows our use of -Z build-std=std,panic_abort flags that make the binary smaller. These flags compile the std library from scratch. 7 | # If solana fixes bpf/sbf-tools, we can revert back to the default behavior (once this PR https://github.com/solana-labs/cargo/pull/1 gets merged and released) 8 | set -eux 9 | 10 | curl https://github.com/guibescos/sbf-tools/releases/download/v1.29.1/solana-bpf-tools-linux.tar.bz2 --output solana-bpf-tools-linux.tar.bz2 -L 11 | mkdir -p ~/.cache/solana/v1.29/bpf-tools/ 12 | tar -xf solana-bpf-tools-linux.tar.bz2 -C ~/.cache/solana/v1.29/bpf-tools/ 13 | rm solana-bpf-tools-linux.tar.bz2 14 | 15 | # Source cargo 16 | if ! which cargo 2> /dev/null 17 | then 18 | # shellcheck disable=SC1090 19 | source "${CARGO_HOME:-$HOME/.cargo}/env" 20 | fi 21 | 22 | # Link the toolchain for future use 23 | rustup toolchain link bpf ~/.cache/solana/v1.29/bpf-tools/rust -------------------------------------------------------------------------------- /program/rust/test_data/ema/audit_overflow.csv: -------------------------------------------------------------------------------- 1 | price,conf,expo,nslots 2 | 5400000000,10,0,5000 3 | 5400000000,10,0,1 4 | 5400000000,10,0,1 5 | 5400000000,10,0,1 6 | 5400000000,10,0,1 7 | 5400000000,10,0,1 8 | 5400000000,10,0,1 9 | 5400000000,10,0,1 10 | 5400000000,10,0,1 11 | 5400000000,10,0,1 12 | 5400000000,10,0,1 13 | 5400000000,10,0,1 14 | 5400000000,10,0,1 15 | 5400000000,10,0,1 16 | 5400000000,10,0,1 17 | 5400000000,10,0,1 18 | 5400000000,10,0,1 19 | 5400000000,10,0,1 20 | 5400000000,10,0,1 21 | 5400000000,10,0,1 22 | 5400000000,10,0,1 23 | 5400000000,10,0,1 24 | 5400000000,10,0,1 25 | 5400000000,10,0,1 26 | 5400000000,10,0,1 27 | 5400000000,10,0,1 28 | 5400000000,10,0,1 29 | 5400000000,10,0,1 30 | 5400000000,10,0,1 31 | 5400000000,10,0,1 32 | 5400000000,10,0,1 33 | 5400000000,10,0,1 34 | 5400000000,10,0,1 35 | 5400000000,10,0,1 36 | 5400000000,10,0,1 37 | 5400000000,10,0,1 38 | 5400000000,10,0,1 39 | 5400000000,10,0,1 40 | 5400000000,10,0,1 41 | 5400000000,10,0,1 42 | 5400000000,10,0,1 43 | 5400000000,10,0,1 44 | 5400000000,10,0,1 45 | 5400000000,10,0,1 46 | 5400000000,10,0,1 47 | 5400000000,10,0,1 48 | 5400000000,10,0,1 49 | 5400000000,10,0,1 50 | 5400000000,10,0,1 51 | 5400000000,10,0,1 52 | 5400000000,10,0,1 53 | 5400000000,10,0,1 54 | 5400000000,10,0,1 55 | 5400000000,10,0,1 56 | 5400000000,10,0,1 57 | 5400000000,10,0,1 58 | 5400000000,10,0,1 59 | -------------------------------------------------------------------------------- /scripts/build-bpf.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Build bpf oracle program : 4 | # ~/pyth-client$ ./scripts/build-bpf.sh 5 | # ~/pyth-client/program$ ../scripts/build-bpf.sh 6 | 7 | set -eu 8 | 9 | PYTH_DIR=$( cd "${1:-.}" && pwd) 10 | 11 | if ! which cargo 2> /dev/null 12 | then 13 | # shellcheck disable=SC1090 14 | source "${CARGO_HOME:-$HOME/.cargo}/env" 15 | fi 16 | 17 | set -x 18 | 19 | # Build both parts of the program (both C and rust) via Cargo 20 | cd "${PYTH_DIR}" 21 | 22 | cargo-test-bpf 23 | cargo-build-bpf -- --locked -Z build-std=std,panic_abort -Z build-std-features=panic_immediate_abort 24 | sha256sum ./target/**/*.so 25 | echo "Checking size of pyth_oracle.so for pythnet" 26 | ./scripts/check-size.sh 88429 27 | mkdir -p target/pyth/pythnet/ 28 | mv target/deploy/pyth_oracle.so target/pyth/pythnet/pyth_oracle_pythnet.so 29 | 30 | # Re-run tests affected by features 31 | cargo-build-bpf -- --locked -Z build-std=std,panic_abort -Z build-std-features=panic_immediate_abort --features no-default-accumulator-v2 32 | cargo test --locked --features no-default-accumulator-v2 33 | sha256sum ./target/**/*.so 34 | echo "Checking size of pyth_oracle.so for pythnet with no accumulator" 35 | ./scripts/check-size.sh 88429 36 | mkdir -p target/pyth/pythnet/ 37 | mv target/deploy/pyth_oracle.so target/pyth/pythnet/pyth_oracle_pythnet_no_accumulator_v2.so 38 | -------------------------------------------------------------------------------- /pc/error.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace pc 7 | { 8 | 9 | // container for general errors 10 | class error 11 | { 12 | public: 13 | error(); 14 | void reset_err(); 15 | bool get_is_err() const; 16 | bool set_err_msg( const std::string& ); 17 | bool set_err_msg( const std::string&, int errcode ); 18 | std::string get_err_msg() const; 19 | private: 20 | bool is_err_; 21 | std::string err_msg_; 22 | }; 23 | 24 | inline error::error() 25 | : is_err_( false ) 26 | { 27 | } 28 | 29 | inline void error::reset_err() 30 | { 31 | is_err_ = false; 32 | err_msg_.clear(); 33 | } 34 | 35 | inline bool error::get_is_err() const 36 | { 37 | return is_err_; 38 | } 39 | 40 | inline bool error::set_err_msg( const std::string& err_msg ) 41 | { 42 | err_msg_ = err_msg; 43 | is_err_ = true; 44 | return false; 45 | } 46 | 47 | inline bool error::set_err_msg( const std::string& err_msg, int errcode ) 48 | { 49 | err_msg_ = err_msg; 50 | err_msg_ += " ["; 51 | err_msg_ += std::to_string( errcode ); 52 | err_msg_ += ' '; 53 | err_msg_ += strerror( errcode); 54 | err_msg_ += ']'; 55 | is_err_ = true; 56 | return false; 57 | } 58 | 59 | inline std::string error::get_err_msg() const 60 | { 61 | return err_msg_; 62 | } 63 | 64 | 65 | } 66 | -------------------------------------------------------------------------------- /pc/capture.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | namespace pc 13 | { 14 | 15 | // capture aggregate price update 16 | class capture : public error 17 | { 18 | public: 19 | 20 | capture(); 21 | ~capture(); 22 | 23 | void set_file( const std::string& ); 24 | std::string get_file() const; 25 | 26 | // start capture thread 27 | bool init(); 28 | 29 | // capture time, account number and account content 30 | void write( pc_pub_key_t *acc, pc_acc_t * ); 31 | 32 | // flush buffer to disk 33 | void flush(); 34 | 35 | public: 36 | void run(); 37 | 38 | private: 39 | 40 | struct PC_PACKED cap_buf { 41 | uint64_t size_; 42 | char buf_[]; 43 | }; 44 | 45 | static const uint64_t max_size = 32*1024; 46 | 47 | cap_buf *alloc(); 48 | 49 | typedef std::vector buf_vec_t; 50 | typedef std::atomic atomic_t; 51 | 52 | cap_buf *curr_; 53 | std::mutex mtx_; 54 | std::thread thrd_; 55 | atomic_t is_run_; 56 | atomic_t is_wtr_; 57 | buf_vec_t pend_; 58 | buf_vec_t done_; 59 | buf_vec_t reuse_; 60 | int fd_; 61 | gzFile zfd_; 62 | std::string file_; 63 | }; 64 | 65 | } 66 | -------------------------------------------------------------------------------- /pctest/setup_pub.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | PYTH=./pyth 4 | SOLANA_DIR="$(dirname $(which cargo-build-bpf))" 5 | SOLANA="$SOLANA_DIR/solana" 6 | SOLANA_KEYGEN="$SOLANA_DIR/solana-keygen" 7 | 8 | SOL_OPT="-u localhost --commitment finalized" 9 | RPC="-r localhost" 10 | FINAL="-c finalized" 11 | 12 | check() 13 | { 14 | CMD=$1 15 | echo "$CMD" 16 | eval $CMD 17 | RC=$? 18 | if [ $RC -ne 0 ]; then 19 | echo "unexpected error executing rc= $RC" 20 | exit 1 21 | fi 22 | } 23 | 24 | setup_pub() 25 | { 26 | ADM=$1 27 | DIR=$2 28 | OPT="-k $DIR $RPC $FINAL" 29 | chmod 0700 $DIR 30 | 31 | # create publisher key and fund it 32 | check "$PYTH init_key $OPT" 33 | check "$SOLANA airdrop 10 -k $DIR/publish_key_pair.json $SOL_OPT" 34 | 35 | # get program public key 36 | prog_id=$($SOLANA_KEYGEN pubkey $ADM/program_key_pair.json) 37 | echo $prog_id > $DIR/program_key.json 38 | chmod 0400 $DIR/program_key.json 39 | 40 | # get mapping public key 41 | map_id=$($SOLANA_KEYGEN pubkey $ADM/mapping_key_pair.json) 42 | echo $map_id > $DIR/mapping_key.json 43 | chmod 0400 $DIR/mapping_key.json 44 | } 45 | 46 | ADIR=$1 47 | PDIR=$2 48 | if [ -z "$ADIR" ] ; then 49 | echo "setup_pub.sh " 50 | exit 1 51 | fi 52 | if [ -z "$PDIR" ] ; then 53 | echo "setup_pub.sh " 54 | exit 1 55 | fi 56 | setup_pub $ADIR $PDIR 57 | -------------------------------------------------------------------------------- /pctest/setup_local.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | PYTH=./pyth 4 | PYTH_ADMIN=./pyth_admin 5 | SOLANA_DIR="$(dirname $(which cargo-build-bpf))" 6 | SOLANA="$SOLANA_DIR/solana" 7 | SOL_OPT="-u localhost --commitment finalized" 8 | RPC="-r localhost" 9 | FINAL="-c finalized" 10 | 11 | check() 12 | { 13 | CMD=$1 14 | echo "$CMD" 15 | eval $CMD 16 | RC=$? 17 | if [ $RC -ne 0 ]; then 18 | echo "unexpected error executing rc= $RC" 19 | exit 1 20 | fi 21 | } 22 | 23 | setup_admin() 24 | { 25 | DIR=$1 26 | OPT="-k $DIR $RPC $FINAL" 27 | chmod 0700 $DIR 28 | 29 | # create publisher key and fund it 30 | check "$PYTH init_key $OPT" 31 | check "$SOLANA airdrop 10 -k $DIR/publish_key_pair.json $SOL_OPT" 32 | 33 | # create program key 34 | check "$PYTH_ADMIN init_program $OPT" 35 | 36 | # setup program key and deploy 37 | check "$SOLANA program deploy ../target/oracle.so -k $DIR/publish_key_pair.json --program-id $DIR/program_key_pair.json $SOL_OPT" 38 | 39 | # setup mapping account 40 | check "$PYTH_ADMIN init_mapping $OPT" 41 | } 42 | 43 | # create key_store directory and initialize key, program and mapping 44 | # accounts assuming you have a local build of solana and are running 45 | # both a solana-validator and solana-faucet on localhost 46 | # run from build/ directory 47 | KDIR=$1 48 | if [ -z "$KDIR" ] ; then 49 | echo "setup_local.sh " 50 | exit 1 51 | fi 52 | setup_admin $KDIR 53 | -------------------------------------------------------------------------------- /pc/replay.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace pc 9 | { 10 | 11 | // replay pyth aggregate prices from capture file 12 | class replay : public error 13 | { 14 | public: 15 | 16 | replay(); 17 | ~replay(); 18 | 19 | // capture file 20 | void set_file( const std::string& cap_file ); 21 | std::string get_file() const; 22 | 23 | // (re) initialize 24 | bool init(); 25 | 26 | // time of price capture 27 | int64_t get_time() const; 28 | 29 | // account number 30 | pc_pub_key_t *get_account() const; 31 | 32 | // on-chain account capture 33 | pc_acc_t *get_update() const; 34 | 35 | // get next price capture 36 | bool get_next(); 37 | 38 | private: 39 | 40 | struct hdr 41 | { 42 | int64_t ts_; 43 | pc_pub_key_t key_; 44 | pc_acc_t acc_; 45 | }; 46 | 47 | hdr *up_; 48 | char *buf_; 49 | size_t pos_; 50 | size_t len_; 51 | gzFile zfd_; 52 | std::string file_; 53 | }; 54 | 55 | inline int64_t replay::get_time() const 56 | { 57 | return up_->ts_; 58 | } 59 | 60 | inline pc_pub_key_t *replay::get_account() const 61 | { 62 | return &up_->key_; 63 | } 64 | 65 | inline pc_acc_t *replay::get_update() const 66 | { 67 | return &up_->acc_; 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /program/rust/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pyth-oracle" 3 | version = "2.35.0" 4 | edition = "2021" 5 | license = "Apache 2.0" 6 | publish = false 7 | 8 | [build-dependencies] 9 | bindgen = "0.60.1" 10 | 11 | [dependencies] 12 | solana-program = "=1.14.17" 13 | bytemuck = "1.11.0" 14 | thiserror = "1.0" 15 | num-derive = "0.3" 16 | num-traits = "0.2" 17 | byteorder = "1.4.3" 18 | serde = { version = "1.0", features = ["derive"], optional = true } 19 | strum = { version = "0.24.1", features = ["derive"], optional = true } 20 | pythnet-sdk = "2.2.0" 21 | solana-sdk = { version = "=1.14.17", optional = true } 22 | bitflags = { version = "2.6.0", features = ["bytemuck"] } 23 | 24 | [dev-dependencies] 25 | solana-program-test = "=1.14.17" 26 | solana-sdk = "=1.14.17" 27 | tokio = "1.14.1" 28 | hex = "0.3.1" 29 | quickcheck = "1" 30 | rand = "0.8.5" 31 | quickcheck_macros = "1" 32 | bincode = "1.3.3" 33 | serde = { version = "1.0", features = ["derive"] } 34 | pythnet-sdk = { version = "2.2.0" , features = ["quickcheck"]} 35 | serde_json = "1.0" 36 | test-generator = "0.3.1" 37 | csv = "1.1" 38 | 39 | # Downgrade to be compatible with Rust 1.60 40 | tracing-subscriber = "=0.3.0" 41 | time-macros = "=0.2.3" 42 | time = "=0.3.7" 43 | 44 | [features] 45 | check = [] # Skips make build in build.rs, use with cargo-clippy and cargo-check 46 | debug = [] 47 | library = ["solana-sdk"] 48 | no-default-accumulator-v2 = [] 49 | 50 | [lib] 51 | crate-type = ["cdylib", "lib"] 52 | -------------------------------------------------------------------------------- /program/rust/test_data/ema/price10_conf1.result: -------------------------------------------------------------------------------- 1 | price,conf,expo,nslots,twap,twac 2 | 1234567890,1,0,5000,1234567890,1 3 | 1234567890,1,0,1,1234567880,1 4 | 1234567890,1,0,1,1234567860,1 5 | 1234567890,1,0,1,1234567840,1 6 | 1234567890,1,0,1,1234567810,1 7 | 1234567890,1,0,1,1234567820,1 8 | 1234567890,1,0,1,1234567830,1 9 | 1234567890,1,0,1,1234567830,1 10 | 1234567890,1,0,1,1234567830,1 11 | 1234567890,1,0,1,1234567830,1 12 | 1234567890,1,0,1,1234567820,1 13 | 1234567890,1,0,1,1234567820,1 14 | 1234567890,1,0,1,1234567820,1 15 | 1234567890,1,0,1,1234567820,1 16 | 1234567890,1,0,1,1234567830,1 17 | 1234567890,1,0,1,1234567830,1 18 | 1234567890,1,0,1,1234567830,1 19 | 1234567890,1,0,1,1234567830,1 20 | 1234567890,1,0,1,1234567830,1 21 | 1234567890,1,0,1,1234567830,1 22 | 1234567890,1,0,1,1234567820,1 23 | 1234567890,1,0,1,1234567800,1 24 | 1234567890,1,0,1,1234567760,1 25 | 1234567890,1,0,1,1234567730,1 26 | 1234567890,1,0,1,1234567660,1 27 | 1234567890,1,0,1,1234567610,1 28 | 1234567890,1,0,1,1234567580,1 29 | 1234567890,1,0,1,1234567570,1 30 | 1234567890,1,0,1,1234567550,1 31 | 1234567890,1,0,1,1234567530,1 32 | 1234567890,1,0,1,1234567540,1 33 | 1234567890,1,0,1,1234567510,1 34 | 1234567890,1,0,1,1234567510,1 35 | 1234567890,1,0,1,1234567510,1 36 | 1234567890,1,0,1,1234567500,1 37 | 1234567890,1,0,1,1234567490,1 38 | 1234567890,1,0,1,1234567470,1 39 | 1234567890,1,0,1,1234567460,1 40 | 1234567890,1,0,1,1234567460,1 41 | 1234567890,1,0,1,1234567440,1 42 | 1234567890,1,0,1,1234567430,1 43 | 1234567890,1,0,1,1234567430,1 44 | 1234567890,1,0,1,1234567440,1 45 | 1234567890,1,0,1,1234567430,1 46 | -------------------------------------------------------------------------------- /program/rust/test_data/aggregation/34.json: -------------------------------------------------------------------------------- 1 | { 2 | "exponent": -8, 3 | "quotes": [ 4 | { 5 | "price": 4289406500000, 6 | "conf": 1453500000, 7 | "slot_diff": -3, 8 | "status": 1 9 | }, 10 | { 11 | "price": 4291320000000, 12 | "conf": 160000000, 13 | "slot_diff": 0, 14 | "status": 1 15 | }, 16 | { 17 | "price": 4290050000000, 18 | "conf": 1700000000, 19 | "slot_diff": -3, 20 | "status": 1 21 | }, 22 | { 23 | "price": 4279480000000, 24 | "conf": 16965586211, 25 | "slot_diff": -3, 26 | "status": 1 27 | }, 28 | { 29 | "price": 4289883000000, 30 | "conf": 2754000000, 31 | "slot_diff": -3, 32 | "status": 1 33 | }, 34 | { 35 | "price": 4290054475519, 36 | "conf": 785800000, 37 | "slot_diff": -4, 38 | "status": 1 39 | }, 40 | { 41 | "price": 4290271973194, 42 | "conf": 2576988362, 43 | "slot_diff": -3, 44 | "status": 1 45 | }, 46 | { 47 | "price": 4290171000000, 48 | "conf": 1000000, 49 | "slot_diff": -7, 50 | "status": 1 51 | }, 52 | { 53 | "price": 4290050000000, 54 | "conf": 500000000, 55 | "slot_diff": -3, 56 | "status": 1 57 | }, 58 | { 59 | "price": 4290171000000, 60 | "conf": 1000000, 61 | "slot_diff": -3, 62 | "status": 1 63 | }, 64 | { 65 | "price": 4290050000000, 66 | "conf": 100001165, 67 | "slot_diff": -3, 68 | "status": 1 69 | } 70 | ] 71 | } 72 | -------------------------------------------------------------------------------- /program/c/src/oracle/sort/tmpl/sort_stable_base.c: -------------------------------------------------------------------------------- 1 | do { /* BEGIN AUTOGENERATED CODE (n= 7) *****************************/ 2 | /* This network of comparators and fallthroughs implement a sorting network representation 3 | of an insertion sort. Each case acts as a sort pass with the fallthrough falling through 4 | to smaller ranges of the input. */ 5 | # define SORT_STABLE_CE(i,j) u = x[(SORT_IDX_T)i]; v = x[(SORT_IDX_T)j]; c = SORT_BEFORE( v, u ); x[(SORT_IDX_T)i] = c ? v : u; x[(SORT_IDX_T)j] = c ? u : v 6 | int c; 7 | SORT_KEY_T u; 8 | SORT_KEY_T v; 9 | switch( n ) { 10 | case (SORT_IDX_T) 7: SORT_STABLE_CE( 0, 1); SORT_STABLE_CE( 1, 2); SORT_STABLE_CE( 2, 3); SORT_STABLE_CE( 3, 4); SORT_STABLE_CE( 4, 5); SORT_STABLE_CE( 5, 6); /* fall through */ 11 | case (SORT_IDX_T) 6: SORT_STABLE_CE( 0, 1); SORT_STABLE_CE( 1, 2); SORT_STABLE_CE( 2, 3); SORT_STABLE_CE( 3, 4); SORT_STABLE_CE( 4, 5); /* fall through */ 12 | case (SORT_IDX_T) 5: SORT_STABLE_CE( 0, 1); SORT_STABLE_CE( 1, 2); SORT_STABLE_CE( 2, 3); SORT_STABLE_CE( 3, 4); /* fall through */ 13 | case (SORT_IDX_T) 4: SORT_STABLE_CE( 0, 1); SORT_STABLE_CE( 1, 2); SORT_STABLE_CE( 2, 3); /* fall through */ 14 | case (SORT_IDX_T) 3: SORT_STABLE_CE( 0, 1); SORT_STABLE_CE( 1, 2); /* fall through */ 15 | case (SORT_IDX_T) 2: SORT_STABLE_CE( 0, 1); /* fall through */ 16 | case (SORT_IDX_T) 1: /* fall through */ 17 | case (SORT_IDX_T) 0: return x; 18 | default: break; 19 | } 20 | # undef SORT_STABLE_CE 21 | } while(0); /* END AUTOGENERATED CODE *******************************/ 22 | -------------------------------------------------------------------------------- /program/rust/src/processor/init_mapping.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::{ 3 | accounts::{ 4 | MappingAccount, 5 | PythAccount, 6 | }, 7 | deserialize::load, 8 | instruction::CommandHeader, 9 | utils::{ 10 | check_permissioned_funding_account, 11 | check_valid_funding_account, 12 | }, 13 | OracleError, 14 | }, 15 | solana_program::{ 16 | account_info::AccountInfo, 17 | entrypoint::ProgramResult, 18 | pubkey::Pubkey, 19 | }, 20 | }; 21 | 22 | /// Initialize first mapping list account 23 | // account[0] funding account [signer writable] 24 | // account[1] mapping account [signer writable] 25 | pub fn init_mapping( 26 | program_id: &Pubkey, 27 | accounts: &[AccountInfo], 28 | instruction_data: &[u8], 29 | ) -> ProgramResult { 30 | let (funding_account, fresh_mapping_account, permissions_account) = match accounts { 31 | [x, y, p] => Ok((x, y, p)), 32 | _ => Err(OracleError::InvalidNumberOfAccounts), 33 | }?; 34 | 35 | let hdr = load::(instruction_data)?; 36 | 37 | check_valid_funding_account(funding_account)?; 38 | check_permissioned_funding_account( 39 | program_id, 40 | fresh_mapping_account, 41 | funding_account, 42 | permissions_account, 43 | hdr, 44 | )?; 45 | 46 | // Initialize by setting to zero again (just in case) and populating the account header 47 | MappingAccount::initialize(fresh_mapping_account, hdr.version)?; 48 | 49 | Ok(()) 50 | } 51 | -------------------------------------------------------------------------------- /pctest/test_mapping.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | using namespace pc; 6 | 7 | int main(int,char**) 8 | { 9 | log::set_level( PC_LOG_DBG_LVL ); 10 | 11 | // get params 12 | key_pair sender; 13 | if (!sender.init_from_file( "/home/richard/test_key_1.json" ) ){ 14 | std::cerr << "failed to find sender" << std::endl; 15 | return 1; 16 | } 17 | key_pair account; 18 | if ( !account.init_from_file( "/home/richard/account_1.json" ) ) { 19 | std::cerr << "failed to find account" << std::endl; 20 | return 1; 21 | } 22 | key_pair prog_id; 23 | if ( !prog_id.init_from_file( "/home/richard/program-id.json" ) ) { 24 | std::cerr << "failed to find program-id" << std::endl; 25 | return 1; 26 | } 27 | uint64_t funds = 5000000000L; // 5 SOL 28 | uint64_t space = 1024; 29 | 30 | // construct connection 31 | pyth_server psvr; 32 | psvr.set_rpc_host( "localhost" ); 33 | psvr.set_listen_port( 8910 ); 34 | psvr.set_dir( "/home/richard/key_store" ); 35 | if ( !psvr.init() ) { 36 | std::cerr << "pythd: " << psvr.get_err_msg() << std::endl; 37 | return 1; 38 | } 39 | // construct request 40 | pyth::create_mapping req; 41 | rpc::create_account *rptr = req.get_create_account(); 42 | rptr->set_sender( sender ); 43 | rptr->set_account( account ); 44 | rptr->set_owner( prog_id ); 45 | rptr->set_lamports( funds ); 46 | rptr->set_space( space ); 47 | 48 | // submit request and poll for results 49 | psvr.submit( &req ); 50 | for(;;) { 51 | psvr.poll(); 52 | } 53 | 54 | return 0; 55 | } 56 | -------------------------------------------------------------------------------- /program/rust/src/tests/test_c_code.rs: -------------------------------------------------------------------------------- 1 | // This file links the various test_*.c files in the C oracle code so they run via cargo. 2 | // Note that most of these tests are exhaustive (testing almost every possible input/output), so 3 | // they take a minute or so to run. 4 | mod c { 5 | #[link(name = "cpyth-test")] 6 | extern "C" { 7 | pub fn test_price_model() -> i32; 8 | pub fn test_sort_stable() -> i32; 9 | pub fn test_align() -> i32; 10 | pub fn test_avg() -> i32; 11 | pub fn test_hash() -> i32; 12 | pub fn test_prng() -> i32; 13 | pub fn test_round() -> i32; 14 | pub fn test_sar() -> i32; 15 | } 16 | } 17 | 18 | #[test] 19 | fn test_price_model() { 20 | unsafe { 21 | assert_eq!(c::test_price_model(), 0); 22 | } 23 | } 24 | 25 | #[test] 26 | fn test_sort_stable() { 27 | unsafe { 28 | assert_eq!(c::test_sort_stable(), 0); 29 | } 30 | } 31 | 32 | #[test] 33 | fn test_align() { 34 | unsafe { 35 | assert_eq!(c::test_align(), 0); 36 | } 37 | } 38 | 39 | #[test] 40 | fn test_avg() { 41 | unsafe { 42 | assert_eq!(c::test_avg(), 0); 43 | } 44 | } 45 | 46 | #[test] 47 | fn test_hash() { 48 | unsafe { 49 | assert_eq!(c::test_hash(), 0); 50 | } 51 | } 52 | 53 | #[test] 54 | fn test_prng() { 55 | unsafe { 56 | assert_eq!(c::test_prng(), 0); 57 | } 58 | } 59 | 60 | #[test] 61 | fn test_round() { 62 | unsafe { 63 | assert_eq!(c::test_round(), 0); 64 | } 65 | } 66 | 67 | #[test] 68 | fn test_sar() { 69 | unsafe { 70 | assert_eq!(c::test_sar(), 0); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /program/rust/src/tests/test_resize_mapping.rs: -------------------------------------------------------------------------------- 1 | use { 2 | super::pyth_simulator::PythSimulator, 3 | crate::{ 4 | accounts::MappingAccount, 5 | error::OracleError, 6 | }, 7 | solana_sdk::{ 8 | instruction::InstructionError, 9 | signer::Signer, 10 | transaction::TransactionError, 11 | }, 12 | std::mem::size_of, 13 | }; 14 | 15 | 16 | #[tokio::test] 17 | async fn test_resize_mapping() { 18 | let mut sim = PythSimulator::new().await; 19 | let mapping_keypair = sim.init_mapping().await.unwrap(); 20 | assert_eq!( 21 | sim.resize_mapping(&mapping_keypair) 22 | .await 23 | .unwrap_err() 24 | .unwrap(), 25 | TransactionError::InstructionError( 26 | 0, 27 | InstructionError::Custom(OracleError::NoNeedToResize as u32) 28 | ) 29 | ); 30 | 31 | sim.truncate_account(mapping_keypair.pubkey(), 20536).await; // Old size. 32 | for i in 0..14 { 33 | println!("resize #{i}"); 34 | sim.resize_mapping(&mapping_keypair).await.unwrap(); 35 | } 36 | assert_eq!( 37 | sim.resize_mapping(&mapping_keypair) 38 | .await 39 | .unwrap_err() 40 | .unwrap(), 41 | TransactionError::InstructionError( 42 | 0, 43 | InstructionError::Custom(OracleError::NoNeedToResize as u32) 44 | ) 45 | ); 46 | assert_eq!( 47 | sim.get_account(mapping_keypair.pubkey()) 48 | .await 49 | .unwrap() 50 | .data 51 | .len(), 52 | size_of::() 53 | ); 54 | } 55 | -------------------------------------------------------------------------------- /pc/log.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #define PC_LOG_DBG_LVL (1U<<3) 6 | #define PC_LOG_INF_LVL (1U<<2) 7 | #define PC_LOG_WRN_LVL (1U<<1) 8 | #define PC_LOG_ERR_LVL (1U<<0) 9 | 10 | #define PC_LOG_TXT(X,LVL) \ 11 | if (pc::log::has_level(LVL)) pc::log::add(X,LVL) 12 | #define PC_LOG_DBG(X) PC_LOG_TXT(X,PC_LOG_DBG_LVL) 13 | #define PC_LOG_INF(X) PC_LOG_TXT(X,PC_LOG_INF_LVL) 14 | #define PC_LOG_WRN(X) PC_LOG_TXT(X,PC_LOG_WRN_LVL) 15 | #define PC_LOG_ERR(X) PC_LOG_TXT(X,PC_LOG_ERR_LVL) 16 | 17 | namespace pc 18 | { 19 | 20 | class log_wtr : public net_wtr 21 | { 22 | public: 23 | void add_i64( int64_t ); 24 | void add_u64( uint64_t ); 25 | void add_f64( double ); 26 | }; 27 | 28 | // log line 29 | class log_line 30 | { 31 | public: 32 | log_line& add( str key, str val ); 33 | log_line& add( str key, const pub_key& val ); 34 | log_line& add( str key, int32_t ); 35 | log_line& add( str key, int64_t ); 36 | log_line& add( str key, uint64_t ); 37 | log_line& add( str key, uint32_t ); 38 | log_line& add( str key, double ); 39 | void end(); 40 | friend class log; 41 | private: 42 | log_line( str, int lvl ); 43 | void add_key( str ); 44 | bool is_first_; 45 | log_wtr wtr_; 46 | }; 47 | 48 | // log reporting 49 | class log 50 | { 51 | public: 52 | 53 | static bool set_log_file( const std::string& ); 54 | static void set_level( int level ); 55 | static bool has_level( int level ); 56 | static log_line add( str topic, int level ); 57 | private: 58 | static int level_; 59 | }; 60 | 61 | inline bool log::has_level( int level ) 62 | { 63 | return level&level_; 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /program/c/src/oracle/util/align.h: -------------------------------------------------------------------------------- 1 | #ifndef _pyth_oracle_util_align_h_ 2 | #define _pyth_oracle_util_align_h_ 3 | 4 | #include "compat_stdint.h" /* For uintptr_t */ 5 | #include /* For alignof */ 6 | 7 | #ifdef __cplusplus 8 | extern "C" { 9 | #endif 10 | 11 | /* align_ispow2 returns non-zero if a is a non-negative integral power 12 | of 2 and zero otherwise. */ 13 | 14 | static inline int 15 | align_ispow2( uintptr_t a ) { 16 | uintptr_t mask = a - (uintptr_t)1; 17 | return (a>(uintptr_t)0) & !(a & mask); 18 | } 19 | 20 | /* align_isaligned returns non-zero if p has byte alignment a. Assumes 21 | align_ispow2(a) is true. */ 22 | 23 | static inline int 24 | align_isaligned( void * p, 25 | uintptr_t a ) { 26 | uintptr_t mask = a - (uintptr_t)1; 27 | return !(((uintptr_t)p) & mask); 28 | } 29 | 30 | /* align_dn/align_up aligns p to the first byte at or before / after p 31 | with alignment a. Assumes align_ispow2(a) is true. */ 32 | 33 | static inline void * 34 | align_dn( void * p, 35 | uintptr_t a ) { 36 | uintptr_t mask = a - (uintptr_t)1; 37 | return (void *)(((uintptr_t)p) & (~mask)); 38 | } 39 | 40 | static inline void * 41 | align_up( void * p, 42 | uintptr_t a ) { 43 | uintptr_t mask = a - (uintptr_t)1; 44 | return (void *)((((uintptr_t)p) + mask) & (~mask)); 45 | } 46 | 47 | /* Helper macros for aligning pointers up or down to compatible 48 | alignments for type T. FIXME: ADD UNIT TEST COVERAGE */ 49 | 50 | #define ALIGN_DN( p, T ) ((T *)align_dn( p, (uintptr_t)alignof(T) )) 51 | #define ALIGN_UP( p, T ) ((T *)align_up( p, (uintptr_t)alignof(T) )) 52 | 53 | #ifdef __cplusplus 54 | } 55 | #endif 56 | 57 | #endif /* _pyth_oracle_util_align_h_ */ 58 | -------------------------------------------------------------------------------- /program/c/src/oracle/util/test_hash.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "util.h" 3 | 4 | uint32_t ref32[10] = { 5 | UINT32_C( 0x00000000 ), 6 | UINT32_C( 0x514e28b7 ), 7 | UINT32_C( 0x30f4c306 ), 8 | UINT32_C( 0x85f0b427 ), 9 | UINT32_C( 0x249cb285 ), 10 | UINT32_C( 0xcc0d53cd ), 11 | UINT32_C( 0x5ceb4d08 ), 12 | UINT32_C( 0x18c9aec4 ), 13 | UINT32_C( 0x4939650b ), 14 | UINT32_C( 0xc27c2913 ) 15 | }; 16 | 17 | 18 | uint64_t ref64[10] = { 19 | UINT64_C( 0x0000000000000000 ), 20 | UINT64_C( 0xb456bcfc34c2cb2c ), 21 | UINT64_C( 0x3abf2a20650683e7 ), 22 | UINT64_C( 0x0b5181c509f8d8ce ), 23 | UINT64_C( 0x47900468a8f01875 ), 24 | UINT64_C( 0xd66ad737d54c5575 ), 25 | UINT64_C( 0xe8b4b3b1c77c4573 ), 26 | UINT64_C( 0x740729cbe468d1dd ), 27 | UINT64_C( 0x46abcca593a3c687 ), 28 | UINT64_C( 0x91209a1ff7f4f1d5 ) 29 | }; 30 | 31 | int test_hash() { 32 | 33 | for( int i=0; i<10; i++ ) { 34 | uint32_t x = (uint32_t)i; 35 | uint32_t y = hash_uint32( x ); 36 | uint32_t z = hash_inverse_uint32( y ); 37 | if( y!=ref32[i] ) { printf( "ref32: FAIL\n" ); return 1; } 38 | if( x!=z ) { printf( "inv32: FAIL\n" ); return 1; } 39 | if( hash_uint32( hash_inverse_uint32( x ) )!=x ) { printf( "INV32: FAIL\n" ); return 1; } 40 | } 41 | 42 | for( int i=0; i<10; i++ ) { 43 | uint64_t x = (uint64_t)i; 44 | uint64_t y = hash_uint64( x ); 45 | uint64_t z = hash_inverse_uint64( y ); 46 | if( y!=ref64[i] ) { printf( "ref64: FAIL\n" ); return 1; } 47 | if( x!=z ) { printf( "inv64: FAIL\n" ); return 1; } 48 | if( hash_uint64( hash_inverse_uint64( x ) )!=x ) { printf( "INV64: FAIL\n" ); return 1; } 49 | } 50 | 51 | /* FIXME: MEASURE AVALANCHE PROPERTIES? */ 52 | return 0; 53 | } 54 | -------------------------------------------------------------------------------- /pc/pub_stats.cpp: -------------------------------------------------------------------------------- 1 | #include "pub_stats.hpp" 2 | #include "misc.hpp" 3 | 4 | using namespace pc; 5 | 6 | pub_stats::pub_stats() 7 | { 8 | clear_stats(); 9 | } 10 | 11 | void pub_stats::clear_stats() 12 | { 13 | num_agg_ = num_sent_ = num_recv_ = num_sub_drop_ = 14 | agg_slot_ = pub_slot_ = 0UL; 15 | __builtin_memset( shist_, 0, sizeof( shist_ ) ); 16 | } 17 | 18 | uint64_t pub_stats::get_num_agg() const 19 | { 20 | return num_agg_; 21 | } 22 | 23 | uint64_t pub_stats::get_num_sent() const 24 | { 25 | return num_sent_; 26 | } 27 | 28 | uint64_t pub_stats::get_num_recv() const 29 | { 30 | return num_recv_; 31 | } 32 | 33 | uint64_t pub_stats::get_num_sub_drop() const 34 | { 35 | return num_sub_drop_; 36 | } 37 | 38 | double pub_stats::get_hit_rate() const 39 | { 40 | return num_sent_ ? (100.*num_recv_)/num_sent_ : 0.; 41 | } 42 | 43 | void pub_stats::add_recv( 44 | uint64_t curr_slot, uint64_t agg_slot, uint64_t pub_slot ) 45 | { 46 | if ( PC_UNLIKELY( num_sent_ == 0UL ) ) { 47 | return; 48 | } 49 | if ( pub_slot_ ) { 50 | uint64_t dslot = curr_slot>pub_slot?curr_slot - pub_slot:0UL; 51 | ++shist_[dslot= pct[j]*num_agg_ ) { 71 | q[j++] = i; 72 | } 73 | } 74 | for( ; j != 4; ++j ) { 75 | q[j] = num_buckets; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /program/c/src/oracle/util/test_sar.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "util.h" 3 | 4 | int 5 | test_sar() { 6 | 7 | prng_t _prng[1]; 8 | prng_t * prng = prng_join( prng_new( _prng, (uint32_t)0, (uint64_t)0 ) ); 9 | 10 | /* FIXME: EXPLICT COVERAGE OF EDGE CASES (PROBABLY STATICALLY FULLY 11 | SAMPLED ALREADY THOUGH FOR 8 AND 16 BIT TYPES) */ 12 | 13 | for( int i=0; i<100000000; i++ ) { 14 | 15 | /* These tests assume the unit test platform has arithmetic right 16 | shift for signed integers (and the diagnostic printfs assume that 17 | long is at least a w-bit wide type) */ 18 | 19 | # define TEST_SAR(w) do { \ 20 | int##w##_t x = (int##w##_t)prng_uint##w( prng ); \ 21 | int n = (int)( prng_uint32( prng ) & (uint32_t)(w-1) ); \ 22 | int##w##_t val = sar_int##w( x, n ); \ 23 | int##w##_t ref = (int##w##_t)(x >> n); \ 24 | if( val!=ref ) { \ 25 | printf( "FAIL (iter %i op %i x %li n %i val %li ref %li)", i, w, (long)x, n, (long)val, (long)ref ); \ 26 | return 1; \ 27 | } \ 28 | } while(0) 29 | 30 | TEST_SAR(8); 31 | TEST_SAR(16); 32 | TEST_SAR(32); 33 | TEST_SAR(64); 34 | # undef TEST_SAR 35 | 36 | } 37 | 38 | prng_delete( prng_leave( prng ) ); 39 | 40 | return 0; 41 | } 42 | -------------------------------------------------------------------------------- /program/rust/src/processor/set_min_pub.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::{ 3 | accounts::PriceAccount, 4 | deserialize::{ 5 | load, 6 | load_checked, 7 | }, 8 | instruction::SetMinPubArgs, 9 | utils::{ 10 | check_permissioned_funding_account, 11 | check_valid_funding_account, 12 | pyth_assert, 13 | }, 14 | OracleError, 15 | }, 16 | solana_program::{ 17 | account_info::AccountInfo, 18 | entrypoint::ProgramResult, 19 | program_error::ProgramError, 20 | pubkey::Pubkey, 21 | }, 22 | std::mem::size_of, 23 | }; 24 | 25 | /// Set min publishers 26 | // account[0] funding account [signer writable] 27 | // account[1] price account [signer writable] 28 | // account[2] permissions account [] 29 | pub fn set_min_pub( 30 | program_id: &Pubkey, 31 | accounts: &[AccountInfo], 32 | instruction_data: &[u8], 33 | ) -> ProgramResult { 34 | let cmd = load::(instruction_data)?; 35 | 36 | pyth_assert( 37 | instruction_data.len() == size_of::(), 38 | ProgramError::InvalidArgument, 39 | )?; 40 | 41 | let (funding_account, price_account, permissions_account) = match accounts { 42 | [x, y, p] => Ok((x, y, p)), 43 | _ => Err(OracleError::InvalidNumberOfAccounts), 44 | }?; 45 | 46 | check_valid_funding_account(funding_account)?; 47 | check_permissioned_funding_account( 48 | program_id, 49 | price_account, 50 | funding_account, 51 | permissions_account, 52 | &cmd.header, 53 | )?; 54 | 55 | 56 | let mut price_account_data = load_checked::(price_account, cmd.header.version)?; 57 | price_account_data.min_pub_ = cmd.minimum_publishers; 58 | 59 | Ok(()) 60 | } 61 | -------------------------------------------------------------------------------- /pc/pub_stats.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace pc 6 | { 7 | 8 | // publish statistics 9 | class pub_stats 10 | { 11 | public: 12 | 13 | pub_stats(); 14 | 15 | // number of prices submitted 16 | uint64_t get_num_sent() const; 17 | 18 | // number of observed aggregate slot updates 19 | uint64_t get_num_agg() const; 20 | 21 | // number of observed publish slot updates 22 | uint64_t get_num_recv() const; 23 | 24 | // number of detected subscription service drop events 25 | // (i.e. where valid slot does not match publish slot of 26 | // previous update). 27 | uint64_t get_num_sub_drop() const; 28 | 29 | // hit rate is 100.*num_recv/num_sent or number of observed publisher 30 | // price updates relative to the number sent 31 | double get_hit_rate() const; 32 | 33 | // get (rough) quartiles of publish end-to-end latency in slots 34 | // up to a maximum of 32 slots 35 | void get_slot_quartiles( uint32_t q[4] ) const; 36 | 37 | // clear-down statistics 38 | void clear_stats(); 39 | 40 | // add slot and the time it was received 41 | void add_recv( uint64_t slot, uint64_t agg_slot, uint64_t pub_slot ); 42 | 43 | // increment subscription drop event 44 | void inc_sub_drop(); 45 | 46 | // increment number of prices sent 47 | void inc_sent(); 48 | 49 | private: 50 | 51 | static constexpr const uint64_t num_buckets = 32; 52 | 53 | uint64_t num_sent_; 54 | uint64_t num_recv_; 55 | uint64_t num_agg_; 56 | uint64_t num_sub_drop_; 57 | uint64_t agg_slot_; 58 | uint64_t pub_slot_; 59 | uint32_t shist_[num_buckets]; 60 | }; 61 | 62 | inline void pub_stats::inc_sub_drop() 63 | { 64 | ++num_sub_drop_; 65 | } 66 | 67 | inline void pub_stats::inc_sent() 68 | { 69 | ++num_sent_; 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /pc/mem_map.cpp: -------------------------------------------------------------------------------- 1 | #include "mem_map.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | using namespace pc; 11 | 12 | mem_map::mem_map() 13 | : fd_(-1), 14 | len_( 0 ), 15 | buf_( nullptr ) 16 | { 17 | } 18 | 19 | mem_map::~mem_map() 20 | { 21 | close(); 22 | } 23 | 24 | void mem_map::close() 25 | { 26 | if ( buf_ ) { 27 | ::munmap( (void*)buf_, len_ ); 28 | buf_ = nullptr; 29 | } 30 | if ( fd_ > 0 ) { 31 | ::close( fd_ ); 32 | fd_ = -1; 33 | } 34 | } 35 | 36 | void mem_map::set_file( const std::string& filen ) 37 | { 38 | file_ = filen; 39 | } 40 | 41 | std::string mem_map::get_file() const 42 | { 43 | return file_; 44 | } 45 | 46 | bool mem_map::remap() 47 | { 48 | struct stat fst[1]; 49 | if ( 0 != fstat( fd_, fst ) || fst->st_size == 0 ) { 50 | return false; 51 | } 52 | assert( fst->st_size >= 0 ); 53 | size_t nlen = static_cast< size_t >( fst->st_size ); 54 | if ( nlen == len_ ) { 55 | return false; 56 | } 57 | void *buf = ::mremap( (void*)buf_, len_, nlen, MREMAP_MAYMOVE ); 58 | if ( buf == MAP_FAILED ) { 59 | return false; 60 | } 61 | buf_ = (const char*)buf; 62 | len_ = nlen; 63 | return true; 64 | } 65 | 66 | bool mem_map::init() 67 | { 68 | close(); 69 | int fd = ::open( file_.c_str(), O_RDONLY ); 70 | if ( fd <= 0 ) { 71 | return false; 72 | } 73 | struct stat fst[1]; 74 | if ( 0 != fstat( fd, fst ) || fst->st_size == 0 ) { 75 | ::close( fd ); 76 | return false; 77 | } 78 | assert( fst->st_size >= 0 ); 79 | len_ = static_cast< size_t >( fst->st_size ); 80 | void *buf = mmap( NULL, len_, PROT_READ, MAP_SHARED, fd, 0 ); 81 | if ( buf == MAP_FAILED ) { 82 | return false; 83 | } 84 | buf_ = (const char*)buf; 85 | fd_ = fd; 86 | return true; 87 | } 88 | -------------------------------------------------------------------------------- /program/rust/src/processor/set_max_latency.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::{ 3 | accounts::PriceAccount, 4 | deserialize::{ 5 | load, 6 | load_checked, 7 | }, 8 | instruction::SetMaxLatencyArgs, 9 | utils::{ 10 | check_permissioned_funding_account, 11 | check_valid_funding_account, 12 | pyth_assert, 13 | }, 14 | OracleError, 15 | }, 16 | solana_program::{ 17 | account_info::AccountInfo, 18 | entrypoint::ProgramResult, 19 | program_error::ProgramError, 20 | pubkey::Pubkey, 21 | }, 22 | std::mem::size_of, 23 | }; 24 | 25 | /// Set max latency 26 | // account[0] funding account [signer writable] 27 | // account[1] price account [signer writable] 28 | // account[2] permissions account [] 29 | pub fn set_max_latency( 30 | program_id: &Pubkey, 31 | accounts: &[AccountInfo], 32 | instruction_data: &[u8], 33 | ) -> ProgramResult { 34 | let cmd = load::(instruction_data)?; // Loading SetMaxLatencyArgs 35 | 36 | pyth_assert( 37 | instruction_data.len() == size_of::(), // Checking size of SetMaxLatencyArgs 38 | ProgramError::InvalidArgument, 39 | )?; 40 | 41 | let (funding_account, price_account, permissions_account) = match accounts { 42 | [x, y, p] => Ok((x, y, p)), 43 | _ => Err(OracleError::InvalidNumberOfAccounts), 44 | }?; 45 | 46 | check_valid_funding_account(funding_account)?; 47 | check_permissioned_funding_account( 48 | program_id, 49 | price_account, 50 | funding_account, 51 | permissions_account, 52 | &cmd.header, 53 | )?; 54 | 55 | let mut price_account_data = load_checked::(price_account, cmd.header.version)?; 56 | price_account_data.max_latency_ = cmd.max_latency; 57 | 58 | Ok(()) 59 | } 60 | -------------------------------------------------------------------------------- /program/rust/src/processor/upd_product.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::{ 3 | accounts::{ 4 | update_product_metadata, 5 | ProductAccount, 6 | }, 7 | deserialize::{ 8 | load, 9 | load_checked, 10 | }, 11 | instruction::CommandHeader, 12 | utils::{ 13 | check_permissioned_funding_account, 14 | check_valid_funding_account, 15 | }, 16 | OracleError, 17 | }, 18 | solana_program::{ 19 | account_info::AccountInfo, 20 | entrypoint::ProgramResult, 21 | pubkey::Pubkey, 22 | }, 23 | }; 24 | 25 | /// Update the metadata associated with a product, overwriting any existing metadata. 26 | /// The metadata is provided as a list of key-value pairs at the end of the `instruction_data`. 27 | // account[0] funding account [signer writable] 28 | // account[1] product account [signer writable] 29 | // account[2] permissions account [] 30 | pub fn upd_product( 31 | program_id: &Pubkey, 32 | accounts: &[AccountInfo], 33 | instruction_data: &[u8], 34 | ) -> ProgramResult { 35 | let (funding_account, product_account, permissions_account) = match accounts { 36 | [x, y, p] => Ok((x, y, p)), 37 | _ => Err(OracleError::InvalidNumberOfAccounts), 38 | }?; 39 | 40 | let hdr = load::(instruction_data)?; 41 | 42 | check_valid_funding_account(funding_account)?; 43 | check_permissioned_funding_account( 44 | program_id, 45 | product_account, 46 | funding_account, 47 | permissions_account, 48 | hdr, 49 | )?; 50 | 51 | 52 | { 53 | // Validate that product_account contains the appropriate account header 54 | let mut _product_data = load_checked::(product_account, hdr.version)?; 55 | } 56 | 57 | update_product_metadata(instruction_data, product_account, hdr.version)?; 58 | 59 | Ok(()) 60 | } 61 | -------------------------------------------------------------------------------- /pc/replay.cpp: -------------------------------------------------------------------------------- 1 | #include "replay.hpp" 2 | 3 | using namespace pc; 4 | 5 | static const size_t buf_sz = 1024*1024; 6 | 7 | replay::replay() 8 | : up_( nullptr ), 9 | buf_( nullptr ), 10 | pos_( 0 ), 11 | len_( 0 ), 12 | zfd_( nullptr ) 13 | { 14 | buf_ = new char[buf_sz]; 15 | } 16 | 17 | replay::~replay() 18 | { 19 | if ( zfd_ ) { 20 | ::gzclose( zfd_ ); 21 | zfd_ = nullptr; 22 | } 23 | if ( buf_ ) { 24 | delete [] buf_; 25 | buf_ = nullptr; 26 | } 27 | } 28 | 29 | void replay::set_file( const std::string& cap_file ) 30 | { 31 | file_ = cap_file; 32 | } 33 | 34 | std::string replay::get_file() const 35 | { 36 | return file_; 37 | } 38 | 39 | bool replay::init() 40 | { 41 | std::string file = file_; 42 | size_t flen = file.length(); 43 | if ( flen >=3 && file.substr(flen-3) != ".gz" ) { 44 | file += ".gz"; 45 | } 46 | zfd_ = ::gzopen( file.c_str(), "r" ); 47 | if ( !zfd_ ) { 48 | return set_err_msg( "failed to open file=" + file ); 49 | } 50 | static const size_t gzb_sz = 128*1024; 51 | if ( 0 != gzbuffer( zfd_, gzb_sz ) ) { 52 | return set_err_msg( 53 | "failed to set compression buffer file=" + file ); 54 | } 55 | pos_ = len_ = 0; 56 | return true; 57 | } 58 | 59 | bool replay::get_next() 60 | { 61 | for(;;) { 62 | size_t left = len_ - pos_; 63 | up_ = (hdr*)&buf_[pos_]; 64 | size_t upsz = sizeof( hdr ); 65 | if ( left >= upsz ) { 66 | upsz = sizeof( int64_t ) + sizeof( pc_pub_key_t ) + up_->acc_.size_; 67 | } 68 | if ( left >= upsz ) { 69 | pos_ += upsz; 70 | return true; 71 | } else { 72 | if ( pos_ ) { 73 | __builtin_memmove( &buf_[0], &buf_[pos_], left ); 74 | } 75 | pos_ = 0; 76 | len_ = left; 77 | int numread = ::gzread( zfd_, &buf_[len_], buf_sz - len_ ); 78 | if ( numread > 0 ) { 79 | len_ += static_cast< size_t >( numread ); 80 | } else { 81 | return false; 82 | } 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /program/c/src/oracle/model/test_price_model.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "../util/util.h" 5 | #include "price_model.h" 6 | 7 | int 8 | qcmp( void const * _p, 9 | void const * _q ) { 10 | int64_t p = *(int64_t const *)_p; 11 | int64_t q = *(int64_t const *)_q; 12 | if( p < q ) return -1; 13 | if( p > q ) return 1; 14 | return 0; 15 | } 16 | 17 | int test_price_model() { 18 | 19 | prng_t _prng[1]; 20 | prng_t * prng = prng_join( prng_new( _prng, (uint32_t)0, (uint64_t)0 ) ); 21 | 22 | # define N 96 23 | 24 | int64_t quote0 [N]; 25 | int64_t quote [N]; 26 | int64_t val [3]; 27 | int64_t scratch[N]; 28 | 29 | for( int iter=0; iter<10000000; iter++ ) { 30 | 31 | /* Generate a random test */ 32 | 33 | uint64_t cnt = (uint64_t)1 + (uint64_t)(prng_uint32( prng ) % (uint32_t)N); /* In [1,N], approx uniform IID */ 34 | for( uint64_t idx=(uint64_t)0; idx>2; 47 | uint64_t p50_idx = cnt>>1; 48 | uint64_t p75_idx = cnt - (uint64_t)1 - p25_idx; 49 | uint64_t is_even = (uint64_t)!(cnt & (uint64_t)1); 50 | 51 | if( val[0]!=quote[ p25_idx ] ) { printf( "FAIL (p25)\n" ); return 1; } 52 | if( val[1]!=avg_2_int64( quote[ p50_idx-is_even ], quote[ p50_idx ] ) ) { printf( "FAIL (p50)\n" ); return 1; } 53 | if( val[2]!=quote[ p75_idx ] ) { printf( "FAIL (p75)\n" ); return 1; } 54 | } 55 | 56 | # undef N 57 | 58 | prng_delete( prng_leave( prng ) ); 59 | 60 | return 0; 61 | } 62 | -------------------------------------------------------------------------------- /program/rust/test_data/ema/1.csv: -------------------------------------------------------------------------------- 1 | price,conf,expo,nslots 2 | 100,10,0,5000 3 | 147,48,0,1 4 | 191,74,0,1 5 | 235,99,0,1 6 | 100,10,0,1 7 | 147,48,0,1 8 | 191,13,0,1 9 | 235,22,0,1 10 | 100,10,0,1 11 | 147,15,0,1 12 | 191,74,0,1 13 | 235,99,0,1 14 | 235,99,0,1 15 | 235,99,0,1 16 | 235,99,0,1 17 | 235,99,0,1 18 | 235,99,0,1 19 | 235,99,0,1 20 | 235,99,0,1 21 | 235,99,0,1 22 | 235,99,0,1 23 | 235,99,0,20 24 | 235,99,0,1 25 | 235,99,0,1 26 | 235,99,0,1 27 | 235,99,0,1 28 | 235,99,0,1 29 | 235,99,0,1 30 | 235,99,0,1 31 | 235,99,0,40 32 | 235,99,0,1 33 | 235,99,0,1 34 | 235,99,0,1 35 | 235,99,0,1 36 | 235,99,0,1 37 | 235,99,0,1 38 | 235,99,0,1 39 | 235,99,0,1 40 | 235,15,0,1 41 | 235,99,0,1 42 | 235,20,0,1 43 | 235,99,0,1 44 | 235,50,0,1 45 | 235,99,0,1 46 | 235,99,0,1 47 | 235,99,0,1 48 | 235,99,0,1 49 | 235,33,0,1 50 | 235,33,0,1 51 | 235,33,0,1 52 | 235,33,0,1 53 | 235,33,0,1 54 | 235,99,0,1 55 | 235,99,0,1 56 | 235,99,0,1 57 | 235,99,0,1 58 | 235,99,0,1 59 | 235,99,0,1 60 | 235,99,0,1 61 | 235,99,0,1 62 | 235,99,0,1 63 | 235,99,0,1 64 | 235,99,0,1 65 | 235,99,0,1 66 | 235,99,0,1 67 | 235,99,0,1 68 | 235,99,0,1 69 | 235,99,0,1 70 | 235,99,0,1 71 | 235,99,0,1 72 | 235,99,0,1 73 | 235,99,0,1 74 | 235,99,0,1 75 | 235,99,0,1 76 | 235,99,0,1 77 | 235,99,0,1 78 | 235,99,0,1 79 | 235,99,0,1 80 | 235,99,0,1 81 | 235,99,0,1 82 | 235,99,0,1 83 | 235,99,0,1 84 | 235,99,0,1 85 | 235,99,0,1 86 | 235,99,0,1 87 | 235,99,0,1 88 | 235,99,0,1 89 | 235,99,0,1 90 | 235,99,0,1 91 | 235,99,0,1 92 | 235,99,0,1 93 | 235,99,0,1 94 | 235,99,0,1 95 | 235,99,0,1 96 | 235,99,0,1 97 | 235,99,0,1 98 | 235,99,0,1 99 | 235,99,0,1 100 | 235,99,0,1 101 | 235,99,0,1 102 | 180,5,0,1 103 | 180,5,0,1 104 | 180,5,0,1 105 | 180,5,0,1 106 | 180,5,0,1 107 | 180,5,0,1 108 | 180,5,0,1 109 | 180,5,0,1 110 | 180,5,0,1 111 | 180,5,0,1 112 | 180,5,0,1 113 | 180,5,0,1 114 | 180,5,0,1 115 | 180,5,0,1 116 | 180,5,0,1 117 | 180,5,0,1 118 | 180,5,0,1 119 | 180,5,0,1 120 | 180,5,0,1 121 | 180,5,0,1 122 | -------------------------------------------------------------------------------- /program/rust/test_data/ema/audit_overflow.result: -------------------------------------------------------------------------------- 1 | price,conf,expo,nslots,twap,twac 2 | 5400000000,10,0,5000,5400000000,10 3 | 5400000000,10,0,1,5399999900,10 4 | 5400000000,10,0,1,5399999900,10 5 | 5400000000,10,0,1,5400000000,10 6 | 5400000000,10,0,1,5399999800,10 7 | 5400000000,10,0,1,5399999800,10 8 | 5400000000,10,0,1,5399999800,10 9 | 5400000000,10,0,1,5399999900,10 10 | 5400000000,10,0,1,5399999800,10 11 | 5400000000,10,0,1,5399999900,10 12 | 5400000000,10,0,1,5399999800,10 13 | 5400000000,10,0,1,5399999900,10 14 | 5400000000,10,0,1,5399999900,10 15 | 5400000000,10,0,1,5399999800,10 16 | 5400000000,10,0,1,5399999900,10 17 | 5400000000,10,0,1,5399999900,10 18 | 5400000000,10,0,1,5399999900,10 19 | 5400000000,10,0,1,5399999900,10 20 | 5400000000,10,0,1,5399999900,10 21 | 5400000000,10,0,1,5399999900,10 22 | 5400000000,10,0,1,5399999900,10 23 | 5400000000,10,0,1,5399999900,10 24 | 5400000000,10,0,1,5399999900,10 25 | 5400000000,10,0,1,5399999900,10 26 | 5400000000,10,0,1,5399999900,10 27 | 5400000000,10,0,1,5399999900,10 28 | 5400000000,10,0,1,5399999900,10 29 | 5400000000,10,0,1,5400000100,10 30 | 5400000000,10,0,1,5400000100,10 31 | 5400000000,10,0,1,5400000200,10 32 | 5400000000,10,0,1,5400000300,10 33 | 5400000000,10,0,1,5400000300,10 34 | 5400000000,10,0,1,5400000300,10 35 | 5400000000,10,0,1,5400000500,10 36 | 5400000000,10,0,1,5400000500,10 37 | 5400000000,10,0,1,5400000500,10 38 | 5400000000,10,0,1,5400000500,10 39 | 5400000000,10,0,1,5400000600,10 40 | 5400000000,10,0,1,5400000700,10 41 | 5400000000,10,0,1,5400000700,10 42 | 5400000000,10,0,1,5400000800,10 43 | 5400000000,10,0,1,5400000800,10 44 | 5400000000,10,0,1,5400000900,10 45 | 5400000000,10,0,1,5400000900,10 46 | 5400000000,10,0,1,5400001000,10 47 | 5400000000,10,0,1,5400001000,10 48 | 5400000000,10,0,1,5400001100,10 49 | 5400000000,10,0,1,5400001100,10 50 | 5400000000,10,0,1,5400001100,10 51 | 5400000000,10,0,1,5400001100,10 52 | 5400000000,10,0,1,5400001100,10 53 | 5400000000,10,0,1,5400001100,10 54 | 5400000000,10,0,1,5400001000,10 55 | 5400000000,10,0,1,5400000900,10 56 | 5400000000,10,0,1,5400000800,10 57 | 5400000000,10,0,1,5400000700,10 58 | 5400000000,10,0,1,5400000600,10 59 | -------------------------------------------------------------------------------- /program/rust/src/processor/resize_mapping.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::{ 3 | accounts::{ 4 | AccountHeader, 5 | MappingAccount, 6 | PythAccount, 7 | }, 8 | c_oracle_header::PC_MAGIC, 9 | deserialize::{ 10 | load, 11 | load_account_as, 12 | }, 13 | instruction::CommandHeader, 14 | utils::{ 15 | check_valid_writable_account, 16 | pyth_assert, 17 | }, 18 | OracleError, 19 | }, 20 | solana_program::{ 21 | account_info::AccountInfo, 22 | entrypoint::{ 23 | ProgramResult, 24 | MAX_PERMITTED_DATA_INCREASE, 25 | }, 26 | pubkey::Pubkey, 27 | }, 28 | std::{ 29 | cmp::min, 30 | mem::size_of, 31 | }, 32 | }; 33 | 34 | /// Resize mapping account. 35 | // account[0] mapping account [writable] 36 | pub fn resize_mapping( 37 | program_id: &Pubkey, 38 | accounts: &[AccountInfo], 39 | instruction_data: &[u8], 40 | ) -> ProgramResult { 41 | let mapping_account = match accounts { 42 | [x] => Ok(x), 43 | _ => Err(OracleError::InvalidNumberOfAccounts), 44 | }?; 45 | 46 | let hdr = load::(instruction_data)?; 47 | 48 | check_valid_writable_account(program_id, mapping_account)?; 49 | 50 | { 51 | let account_header = load_account_as::(mapping_account)?; 52 | pyth_assert( 53 | account_header.magic_number == PC_MAGIC 54 | && account_header.version == hdr.version 55 | && account_header.account_type == MappingAccount::ACCOUNT_TYPE, 56 | OracleError::InvalidAccountHeader.into(), 57 | )?; 58 | } 59 | 60 | pyth_assert( 61 | mapping_account.data_len() < size_of::(), 62 | OracleError::NoNeedToResize.into(), 63 | )?; 64 | let new_size = min( 65 | size_of::(), 66 | mapping_account.data_len() + MAX_PERMITTED_DATA_INCREASE, 67 | ); 68 | mapping_account.realloc(new_size, true)?; 69 | 70 | Ok(()) 71 | } 72 | -------------------------------------------------------------------------------- /pc/key_store.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace pc 8 | { 9 | 10 | class key_store : public error 11 | { 12 | public: 13 | 14 | key_store(); 15 | 16 | // create/chmod key_store directory 17 | bool create(); 18 | 19 | // initialize 20 | bool init(); 21 | 22 | // directory where to store account keys 23 | void set_dir( const std::string& dir_name ); 24 | std::string get_dir() const; 25 | 26 | // file names 27 | std::string get_publish_key_pair_file() const; 28 | std::string get_mapping_key_pair_file() const; 29 | std::string get_mapping_pub_key_file() const; 30 | std::string get_program_key_pair_file() const; 31 | std::string get_program_pub_key_file() const; 32 | 33 | // primary publishing and funding key 34 | key_pair *create_publish_key_pair(); 35 | key_pair *get_publish_key_pair(); 36 | key_cache*get_publish_key_cache(); 37 | pub_key *get_publish_pub_key(); 38 | 39 | // get mapping key_pair or public key 40 | key_pair *create_mapping_key_pair(); 41 | key_pair *get_mapping_key_pair(); 42 | pub_key *get_mapping_pub_key(); 43 | 44 | // get program_id public key 45 | key_pair *create_program_key_pair(); 46 | key_pair *get_program_key_pair(); 47 | pub_key *get_program_pub_key(); 48 | 49 | // create new mapping or symbol account 50 | bool create_account_key_pair( key_pair& ); 51 | bool get_account_key_pair( const pub_key&, key_pair& ); 52 | 53 | private: 54 | 55 | bool has_pkey_; 56 | bool has_mkey_; 57 | bool has_mpub_; 58 | bool has_gkey_; 59 | bool has_gpub_; 60 | key_pair pkey_; // primary publishing and funding key 61 | key_pair mkey_; // mapping account key 62 | key_pair gkey_; // program key pair 63 | key_cache ckey_; // publishing cache key 64 | pub_key ppub_; // publisher public key 65 | pub_key mpub_; // mapping account public key 66 | pub_key gpub_; // program id 67 | std::string dir_; // key store directory 68 | }; 69 | 70 | } 71 | -------------------------------------------------------------------------------- /program/rust/src/tests/test_message.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::accounts::PythOracleSerialize, 3 | byteorder::BigEndian, 4 | pythnet_sdk::{ 5 | messages::{ 6 | Message, 7 | PriceFeedMessage, 8 | PublisherStakeCapsMessage, 9 | TwapMessage, 10 | }, 11 | wire::from_slice, 12 | }, 13 | quickcheck::{ 14 | Gen, 15 | QuickCheck, 16 | }, 17 | quickcheck_macros::quickcheck, 18 | }; 19 | 20 | #[quickcheck] 21 | fn test_price_feed_message_roundtrip(input: PriceFeedMessage) -> bool { 22 | let reconstructed = from_slice::(&input.to_bytes()).unwrap(); 23 | 24 | println!("Failed test case:"); 25 | println!("{:?}", input); 26 | println!("{:?}", reconstructed); 27 | match reconstructed { 28 | Message::PriceFeedMessage(reconstructed) => reconstructed == input, 29 | _ => false, 30 | } 31 | } 32 | 33 | #[quickcheck] 34 | fn test_twap_message_roundtrip(input: TwapMessage) -> bool { 35 | let reconstructed = from_slice::(&input.to_bytes()).unwrap(); 36 | 37 | println!("Failed test case:"); 38 | println!("{:?}", input); 39 | println!("{:?}", reconstructed); 40 | 41 | match reconstructed { 42 | Message::TwapMessage(reconstructed) => reconstructed == input, 43 | _ => false, 44 | } 45 | } 46 | 47 | 48 | fn prop_publisher_caps_message_roundtrip(input: PublisherStakeCapsMessage) -> bool { 49 | let reconstructed = from_slice::(&input.clone().to_bytes()).unwrap(); 50 | 51 | println!("Failed test case:"); 52 | println!("{:?}", input); 53 | println!("{:?}", reconstructed); 54 | 55 | match reconstructed { 56 | Message::PublisherStakeCapsMessage(reconstructed) => reconstructed == input, 57 | _ => false, 58 | } 59 | } 60 | 61 | #[test] 62 | fn test_publisher_caps_message_roundtrip() { 63 | // Configure the size parameter for the generator 64 | QuickCheck::new() 65 | .gen(Gen::new(1024)) 66 | .quickcheck(prop_publisher_caps_message_roundtrip as fn(PublisherStakeCapsMessage) -> bool); 67 | } 68 | -------------------------------------------------------------------------------- /pcapps/tx_rpc_client.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace pc { 6 | 7 | namespace rpc { 8 | 9 | // get validator node health 10 | class get_health : public rpc_request 11 | { 12 | public: 13 | void request( json_wtr& ) override; 14 | void response( const jtree& ) override; 15 | }; 16 | 17 | // get mapping of node id to ip address and port 18 | class get_cluster_nodes : public rpc_request 19 | { 20 | public: 21 | bool get_ip_addr( const pub_key&, ip_addr& ); 22 | void request( json_wtr& ) override; 23 | void response( const jtree&p) override; 24 | private: 25 | struct trait_node { 26 | static const size_t hsize_ = 859UL; 27 | typedef uint32_t idx_t; 28 | typedef pub_key key_t; 29 | typedef const pub_key& keyref_t; 30 | typedef ip_addr val_t; 31 | struct hash_t { 32 | idx_t operator() ( keyref_t a ) { 33 | uint64_t *i = (uint64_t*)a.data(); 34 | return i[0]; 35 | } 36 | }; 37 | }; 38 | typedef hash_map node_map_t; 39 | node_map_t nmap_; 40 | }; 41 | 42 | // get id of leader node by slot 43 | class get_slot_leaders : public rpc_request 44 | { 45 | public: 46 | get_slot_leaders(); 47 | void set_slot(uint64_t slot); 48 | void set_limit( uint64_t limit ); 49 | pub_key *get_leader( uint64_t ); 50 | uint64_t get_last_slot() const; 51 | void request( json_wtr& ) override; 52 | void response( const jtree& ) override; 53 | private: 54 | typedef std::vector ldr_vec_t; 55 | uint64_t rslot_; 56 | uint64_t limit_; 57 | uint64_t lslot_; 58 | ldr_vec_t lvec_; 59 | }; 60 | 61 | // find out when slots update 62 | class slot_subscribe : public rpc_subscription 63 | { 64 | public: 65 | uint64_t get_slot() const; 66 | void request( json_wtr& ) override; 67 | void response( const jtree& ) override; 68 | bool notify( const jtree& ) override; 69 | private: 70 | uint64_t slot_; 71 | }; 72 | 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /program/c/src/oracle/util/sar.h: -------------------------------------------------------------------------------- 1 | #ifndef _pyth_oracle_util_sar_h_ 2 | #define _pyth_oracle_util_sar_h_ 3 | 4 | /* Portable arithmetic shift right. */ 5 | 6 | #include "compat_stdint.h" 7 | 8 | /* Define PYTH_ORACLE_UTIL_SAR_USE_PLATFORM to non-zero if the platform 9 | does arithmetic right shift signed integer and zero if the platform 10 | does not or is not known to. PYTH_ORACLE_UTIL_SAR_USE_PLATFORM 11 | defaults to zero. */ 12 | 13 | #ifndef PYTH_ORACLE_UTIL_SAR_USE_PLATFORM 14 | #define PYTH_ORACLE_UTIL_SAR_USE_PLATFORM 0 15 | #endif 16 | 17 | #ifdef __cplusplus 18 | extern "C" { 19 | #endif 20 | 21 | static inline int8_t 22 | sar_int8( int8_t x, 23 | int n ) { /* Assumed in [0,7] */ 24 | # if PYTH_ORACLE_UTIL_SAR_USE_PLATFORM 25 | return x >> n; 26 | # else 27 | uint8_t ux = (uint8_t)x; 28 | uint8_t s = (uint8_t)-(ux >> 7); /* get the sign, ((int8_t)s)==-1 if x<0 and 0 otherwise */ 29 | return (int8_t)(((ux ^ s) >> n) ^ s); 30 | # endif 31 | } 32 | 33 | static inline int16_t 34 | sar_int16( int16_t x, 35 | int n ) { /* Assumed in [0,15] */ 36 | # if PYTH_ORACLE_UTIL_SAR_USE_PLATFORM 37 | return x >> n; 38 | # else 39 | uint16_t ux = (uint16_t)x; 40 | uint16_t s = (uint16_t)-(ux >> 15); /* get the sign, ((int16_t)s)==-1 if x<0 and 0 otherwise */ 41 | return (int16_t)(((ux ^ s) >> n) ^ s); 42 | # endif 43 | } 44 | 45 | static inline int32_t 46 | sar_int32( int32_t x, 47 | int n ) { /* Assumed in [0,31] */ 48 | # if PYTH_ORACLE_UTIL_SAR_USE_PLATFORM 49 | return x >> n; 50 | # else 51 | uint32_t ux = (uint32_t)x; 52 | uint32_t s = -(ux >> 31); /* get the sign, ((int32_t)s)==-1 if x<0 and 0 otherwise */ 53 | return (int32_t)(((ux ^ s) >> n) ^ s); 54 | # endif 55 | } 56 | 57 | static inline int64_t 58 | sar_int64( int64_t x, 59 | int n ) { /* Assumed in [0,63] */ 60 | # if PYTH_ORACLE_UTIL_SAR_USE_PLATFORM 61 | return x >> n; 62 | # else 63 | uint64_t ux = (uint64_t)x; 64 | uint64_t s = -(ux >> 63); /* get the sign, ((int64_t)s)==-1 if x<0 and 0 otherwise */ 65 | return (int64_t)(((ux ^ s) >> n) ^ s); 66 | # endif 67 | } 68 | 69 | #ifdef __cplusplus 70 | } 71 | #endif 72 | 73 | #endif /* _pyth_oracle_util_sar_h_ */ 74 | -------------------------------------------------------------------------------- /program/c/src/oracle/util/hash.h: -------------------------------------------------------------------------------- 1 | #ifndef _pyth_oracle_util_hash_h_ 2 | #define _pyth_oracle_util_hash_h_ 3 | 4 | /* Useful hashing utilities */ 5 | 6 | #include "compat_stdint.h" 7 | 8 | #ifdef __cplusplus 9 | extern "C" { 10 | #endif 11 | 12 | /* High quality (full avalanche) high speed integer to integer hashing. 13 | hash_uint32 has the properties that [0,2^32) hashes to a random 14 | looking permutation of [0,2^32) and hash(0)==0. Similarly for 15 | hash_uint64. Based on Google's Murmur3 hash finalizer (public 16 | domain). Not cryptographically secure but passes various strict 17 | tests of randomness when used as a PRNG (see prng.h). */ 18 | 19 | static inline uint32_t 20 | hash_uint32( uint32_t x ) { 21 | x ^= x >> 16; 22 | x *= UINT32_C( 0x85ebca6b ); 23 | x ^= x >> 13; 24 | x *= UINT32_C( 0xc2b2ae35 ); 25 | x ^= x >> 16; 26 | return x; 27 | } 28 | 29 | static inline uint64_t 30 | hash_uint64( uint64_t x ) { 31 | x ^= x >> 33; 32 | x *= UINT64_C( 0xff51afd7ed558ccd ); 33 | x ^= x >> 33; 34 | x *= UINT64_C( 0xc4ceb9fe1a85ec53 ); 35 | x ^= x >> 33; 36 | return x; 37 | } 38 | 39 | /* Inverses of the above. E.g.: 40 | hash_inverse_uint32( hash_uint32( (uint32_t)x ) )==(uint32_t)x 41 | and: 42 | hash_uint32( hash_inverse_uint32( (uint32_t)x ) )==(uint32_t)x 43 | Similarly for hash_inverse_uint64. These by themselves are similar 44 | quality hashes to the above and having the inverses of the above can 45 | be useful. The fact these have (nearly) identical operations / 46 | operation counts concretely demonstrates that none of these are 47 | standalone cryptographically secure. */ 48 | 49 | static inline uint32_t 50 | hash_inverse_uint32( uint32_t x ) { 51 | x ^= x >> 16; 52 | x *= UINT32_C( 0x7ed1b41d ); 53 | x ^= (x >> 13) ^ (x >> 26); 54 | x *= UINT32_C( 0xa5cb9243 ); 55 | x ^= x >> 16; 56 | return x; 57 | } 58 | 59 | static inline uint64_t 60 | hash_inverse_uint64( uint64_t x ) { 61 | x ^= x >> 33; 62 | x *= UINT64_C( 0x9cb4b2f8129337db ); 63 | x ^= x >> 33; 64 | x *= UINT64_C( 0x4f74430c22a54005 ); 65 | x ^= x >> 33; 66 | return x; 67 | } 68 | 69 | #ifdef __cplusplus 70 | } 71 | #endif 72 | 73 | #endif /* _pyth_oracle_util_hash_h_ */ 74 | -------------------------------------------------------------------------------- /program/rust/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Allow non upper case globals from C 2 | #![allow(non_upper_case_globals)] 3 | 4 | mod accounts; 5 | mod c_oracle_header; 6 | mod deserialize; 7 | mod error; 8 | mod instruction; 9 | mod processor; 10 | mod utils; 11 | 12 | #[cfg(any(test, feature = "library"))] 13 | pub mod validator; 14 | 15 | #[cfg(feature = "library")] 16 | pub use solana_program; 17 | 18 | #[cfg(test)] 19 | mod tests; 20 | 21 | #[cfg(feature = "debug")] 22 | mod log; 23 | 24 | // When compiled in `library` mode the on-chain definitions provided by this library are 25 | // exported. This is useful when other libraries wish to directly utilise the exact Solana specific 26 | // on-chain definitions of the Pyth program. 27 | // 28 | // While we have `pyth-sdk-rs` which exposes a more friendly interface, this is still useful when a 29 | // downstream user wants to confirm for example that they can compile against the binary interface 30 | // of this program for their specific solana version. 31 | pub use crate::error::OracleError; 32 | #[cfg(feature = "strum")] 33 | pub use accounts::MessageType; 34 | #[cfg(feature = "library")] 35 | pub use accounts::{ 36 | AccountHeader, 37 | MappingAccount, 38 | PermissionAccount, 39 | PriceAccount, 40 | PriceAccountFlags, 41 | PriceComponent, 42 | PriceEma, 43 | PriceInfo, 44 | ProductAccount, 45 | PythAccount, 46 | PythOracleSerialize, 47 | }; 48 | #[cfg(feature = "library")] 49 | pub use { 50 | processor::find_publisher_index, 51 | utils::get_status_for_conf_price_ratio, 52 | }; 53 | use { 54 | processor::process_instruction, 55 | solana_program::entrypoint, 56 | }; 57 | 58 | // Below is a high level description of the rust/c setup. 59 | 60 | // As we migrate from C to Rust, our Rust code needs to be able to interact with C. 61 | // build-bpf.sh is set up to compile the C code into a two archive files 62 | // contained in `./program/c/target/` 63 | // - `libcpyth-bpf.a` contains the bpf version for production code 64 | // - `libcpyth-native.a` contains the systems architecture version for tests 65 | 66 | // We also generate bindings for the constants in oracle.h (as well as other things 67 | // included in bindings.h). 68 | 69 | entrypoint!(process_instruction); 70 | -------------------------------------------------------------------------------- /program/c/makefile: -------------------------------------------------------------------------------- 1 | # Use the OUT_DIR env variable as the output directory if available, otherwise 2 | # default to ./target 3 | OUT_DIR := $(if $(OUT_DIR),$(OUT_DIR),./target) 4 | SOLANA := $(shell dirname $(shell which cargo-build-bpf)) 5 | 6 | ifneq ("$(SOLANA)","") 7 | ifneq ("$(wildcard $(SOLANA)/sdk/bpf/c/bpf.mk)","") 8 | $(info using Solana BPF SDK) 9 | include $(SOLANA)/sdk/bpf/c/bpf.mk 10 | else 11 | $(info using Solana SBF SDK) 12 | include $(SOLANA)/sdk/sbf/c/sbf.mk 13 | endif 14 | endif 15 | 16 | FEATURES_H_BODY:="\#pragma once" 17 | 18 | 19 | .PHONY: features.h # Putting this in .PHONY makes sure the header is always regenerated 20 | features.h: 21 | echo $(FEATURES_H_BODY) > src/oracle/features.h 22 | 23 | 24 | # Bundle C code compiled to bpf for use by rust 25 | # The all target is defined by the solana makefile included above and generates the needed .o file. 26 | .PHONY: cpyth-bpf 27 | cpyth-bpf: features.h all 28 | bash -c "ar rc $(OUT_DIR)/libcpyth-bpf.a $(OUT_DIR)/oracle/*.o" 29 | 30 | 31 | # 2-Stage Contract Build 32 | # 33 | # 1) Compile C code to system architecture for use by rust's cargo test 34 | # 2) Bundle C code compiled to system architecture for use by rust's cargo test 35 | .PHONY: cpyth-native 36 | cpyth-native: features.h 37 | gcc -c ./src/oracle/native/upd_aggregate.c -o $(OUT_DIR)/cpyth-native.o -fPIC 38 | ar rcs $(OUT_DIR)/libcpyth-native.a $(OUT_DIR)/cpyth-native.o 39 | 40 | 41 | # Note: there's probably a smart way to do this with wildcards but I (jayant) can't figure it out 42 | .PHONY: test 43 | test: features.h 44 | mkdir -p $(OUT_DIR)/test/ 45 | gcc -c ./src/oracle/model/test_price_model.c -o $(OUT_DIR)/test/test_price_model.o -fPIC 46 | gcc -c ./src/oracle/sort/test_sort_stable.c -o $(OUT_DIR)/test/test_sort_stable.o -fPIC 47 | gcc -c ./src/oracle/util/test_align.c -o $(OUT_DIR)/test/test_align.o -fPIC 48 | gcc -c ./src/oracle/util/test_avg.c -o $(OUT_DIR)/test/test_avg.o -fPIC 49 | gcc -c ./src/oracle/util/test_hash.c -o $(OUT_DIR)/test/test_hash.o -fPIC 50 | gcc -c ./src/oracle/util/test_prng.c -o $(OUT_DIR)/test/test_prng.o -fPIC 51 | gcc -c ./src/oracle/util/test_round.c -o $(OUT_DIR)/test/test_round.o -fPIC 52 | gcc -c ./src/oracle/util/test_sar.c -o $(OUT_DIR)/test/test_sar.o -fPIC 53 | ar rcs $(OUT_DIR)/libcpyth-test.a $(OUT_DIR)/test/*.o 54 | -------------------------------------------------------------------------------- /program/c/src/oracle/util/test_align.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "util.h" 4 | 5 | int 6 | test_align() { 7 | 8 | uint32_t shift_mask = (uint32_t)(sizeof(uintptr_t)*(size_t)CHAR_BIT); 9 | if( !align_ispow2( (uintptr_t)shift_mask ) ) { 10 | printf( "FAIL (platform)\n" ); 11 | return 1; 12 | } 13 | shift_mask--; 14 | 15 | prng_t _prng[1]; 16 | prng_t * prng = prng_join( prng_new( _prng, (uint32_t)0, (uint64_t)0 ) ); 17 | 18 | for( int i=0; i<1000000000; i++ ) { 19 | 20 | /* Test align_ispow2 */ 21 | 22 | do { 23 | uint32_t r = prng_uint32( prng ); 24 | uint32_t s = (r>>8) & shift_mask; /* s is uniform IID valid right shift amount for a uintptr_t */ 25 | r &= (uint32_t)255; /* r is 8-bit uniform IID rand (power of 2 with prob ~8/256=1/32) */ 26 | uintptr_t x = ((uintptr_t)r) << s; /* x is power of 2 with prob ~1/32 */ 27 | 28 | int c = align_ispow2( x ); 29 | int d = (x>(uintptr_t)0) && !(x & (x-(uintptr_t)1)); 30 | if( c!=d ) { 31 | printf( "FAIL (x %lx c %i d %i)\n", (unsigned long)x, c, d ); 32 | return 1; 33 | } 34 | } while(0); 35 | 36 | /* Test align_up/align_dn/align_isaligned */ 37 | 38 | do { 39 | uintptr_t a = ((uintptr_t)1) << (prng_uint32( prng ) & shift_mask); 40 | 41 | uintptr_t x = (uintptr_t)prng_uint64( prng ); 42 | void * u = (void *)x; 43 | void * v = align_dn( u, a ); 44 | void * w = align_up( u, a ); 45 | uintptr_t y = (uintptr_t)v; 46 | uintptr_t z = (uintptr_t)w; 47 | 48 | int already_aligned = (u==v); 49 | int already_aligned_1 = (u==w); 50 | 51 | uintptr_t mask = a-(uintptr_t)1; 52 | if( already_aligned != already_aligned_1 || 53 | align_isaligned( u, a )!=already_aligned || 54 | !align_isaligned( v, a ) || 55 | !align_isaligned( w, a ) || 56 | ( x & ~mask) != y || 57 | ((x+mask) & ~mask) != z ) { 58 | printf( "FAIL (a %lx aa %i aa1 %i x %lx y %lx z %lx)\n", 59 | (unsigned long)a, already_aligned, already_aligned_1, (unsigned long)x, (unsigned long)y, (unsigned long)z ); 60 | return 1; 61 | } 62 | 63 | } while(0); 64 | } 65 | 66 | prng_delete( prng_leave( prng ) ); 67 | 68 | return 0; 69 | } 70 | -------------------------------------------------------------------------------- /program/rust/src/tests/test_del_price.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::{ 3 | accounts::ProductAccount, 4 | tests::pyth_simulator::PythSimulator, 5 | }, 6 | solana_program::pubkey::Pubkey, 7 | solana_sdk::signer::Signer, 8 | }; 9 | 10 | #[tokio::test] 11 | async fn test_del_price() { 12 | let mut sim = PythSimulator::new().await; 13 | let mapping_keypair = sim.init_mapping().await.unwrap(); 14 | let product1 = sim.add_product(&mapping_keypair).await.unwrap(); 15 | let product2 = sim.add_product(&mapping_keypair).await.unwrap(); 16 | let product3 = sim.add_product(&mapping_keypair).await.unwrap(); 17 | let price1 = sim.add_price(&product1, -8).await.unwrap(); 18 | let price2_1 = sim.add_price(&product2, -8).await.unwrap(); 19 | let price2_2 = sim.add_price(&product2, -8).await.unwrap(); 20 | 21 | assert!(sim.get_account(price1.pubkey()).await.is_some()); 22 | assert!(sim.get_account(price2_1.pubkey()).await.is_some()); 23 | 24 | assert!(sim.del_price(&product2, &price1).await.is_err()); 25 | assert!(sim.del_price(&product1, &price2_1).await.is_err()); 26 | assert!(sim.del_price(&product1, &price2_2).await.is_err()); 27 | assert!(sim.del_price(&product3, &price2_1).await.is_err()); 28 | assert!(sim.del_price(&product3, &price2_2).await.is_err()); 29 | 30 | sim.del_price(&product1, &price1).await.unwrap(); 31 | assert!(sim.get_account(price1.pubkey()).await.is_none()); 32 | 33 | let product1_data = sim 34 | .get_account_data_as::(product1.pubkey()) 35 | .await 36 | .unwrap(); 37 | assert!(product1_data.first_price_account == Pubkey::default()); 38 | 39 | 40 | // price2_1 is the 2nd item in the linked list since price2_2 got added after t. 41 | assert!(sim.del_price(&product2, &price2_1).await.is_err()); 42 | // Can delete the accounts in the opposite order though 43 | assert!(sim.del_price(&product2, &price2_2).await.is_ok()); 44 | assert!(sim.del_price(&product2, &price2_1).await.is_ok()); 45 | 46 | assert!(sim.get_account(price2_2.pubkey()).await.is_none()); 47 | assert!(sim.get_account(price2_1.pubkey()).await.is_none()); 48 | 49 | let product2_data = sim 50 | .get_account_data_as::(product2.pubkey()) 51 | .await 52 | .unwrap(); 53 | 54 | assert!(product2_data.first_price_account == Pubkey::default()); 55 | } 56 | -------------------------------------------------------------------------------- /program/rust/src/tests/test_check_valid_signable_account_or_permissioned_funding_account.rs: -------------------------------------------------------------------------------- 1 | use { 2 | super::test_utils::AccountSetup, 3 | crate::{ 4 | accounts::{ 5 | PermissionAccount, 6 | ProductAccount, 7 | PythAccount, 8 | }, 9 | c_oracle_header::PC_VERSION, 10 | instruction::{ 11 | CommandHeader, 12 | OracleCommand, 13 | }, 14 | utils::check_permissioned_funding_account, 15 | }, 16 | solana_sdk::signature::{ 17 | Keypair, 18 | Signer, 19 | }, 20 | }; 21 | 22 | /// NOTE(2023-11-19): This check was recently modified to throw errors 23 | /// on missing permissions account. This simple test case ensures that 24 | /// a missing permissions account is causing failures unconditionally. 25 | #[test] 26 | pub fn test_permissions_account_mandatory() { 27 | let program_kp = Keypair::new(); 28 | let program_id = program_kp.pubkey(); 29 | 30 | let prod_kp = Keypair::new(); 31 | let mut prod_setup = AccountSetup::new::(&prod_kp.pubkey()); 32 | let mut prod_account = prod_setup.as_account_info(); 33 | 34 | prod_account.is_writable = true; 35 | prod_account.is_signer = true; 36 | prod_account.owner = &program_id; 37 | 38 | let mut funding_setup = AccountSetup::new_funding(); 39 | let mut funding_account = funding_setup.as_account_info(); 40 | 41 | funding_account.is_writable = true; 42 | funding_account.is_signer = true; 43 | 44 | let mut permissions_setup = AccountSetup::new_permission(&program_kp.pubkey()); 45 | let permissions_account = permissions_setup.as_account_info(); 46 | 47 | { 48 | let mut permissions_account_data = 49 | PermissionAccount::initialize(&permissions_account, PC_VERSION).unwrap(); 50 | permissions_account_data.master_authority = *funding_account.key; 51 | permissions_account_data.data_curation_authority = *funding_account.key; 52 | permissions_account_data.security_authority = *funding_account.key; 53 | } 54 | 55 | assert_eq!( 56 | check_permissioned_funding_account( 57 | &program_kp.pubkey(), 58 | &prod_account, 59 | &funding_account, 60 | &permissions_account, 61 | &CommandHeader { 62 | version: PC_VERSION, 63 | command: OracleCommand::UpdProduct as i32, 64 | }, 65 | ), 66 | Ok(()), 67 | ); 68 | } 69 | -------------------------------------------------------------------------------- /program/rust/src/error.rs: -------------------------------------------------------------------------------- 1 | //! Error types 2 | use { 3 | solana_program::program_error::ProgramError, 4 | thiserror::Error, 5 | }; 6 | 7 | /// Errors that may be returned by the oracle program 8 | #[derive(Clone, Debug, Eq, Error, PartialEq)] 9 | pub enum OracleError { 10 | /// Generic catch all error 11 | #[error("Generic")] 12 | Generic = 600, 13 | /// integer casting error 14 | #[error("IntegerCastingError")] 15 | IntegerCastingError = 601, 16 | /// c_entrypoint returned an unexpected value 17 | #[error("UnknownCError")] 18 | UnknownCError = 602, 19 | #[error("UnrecognizedInstruction")] 20 | UnrecognizedInstruction = 603, 21 | #[error("InvalidFundingAccount")] 22 | InvalidFundingAccount = 604, 23 | #[error("InvalidSignableAccount")] 24 | InvalidSignableAccount = 605, 25 | #[error("InvalidSystemAccount")] 26 | InvalidSystemAccount = 606, 27 | #[error("InvalidWritableAccount")] 28 | InvalidWritableAccount = 607, 29 | #[error("InvalidFreshAccount")] 30 | InvalidFreshAccount = 608, 31 | #[error("InvalidInstructionVersion")] 32 | InvalidInstructionVersion = 609, 33 | #[error("InstructionDataTooShort")] 34 | InstructionDataTooShort = 610, 35 | #[error("InstructionDataSliceMisaligned")] 36 | InstructionDataSliceMisaligned = 611, 37 | #[error("AccountTooSmall")] 38 | AccountTooSmall = 612, 39 | #[error("DeserializationError")] 40 | DeserializationError = 613, 41 | #[error("FailedAuthenticatingUpgradeAuthority")] 42 | InvalidUpgradeAuthority = 614, 43 | #[error("FailedPdaVerification")] 44 | InvalidPda = 615, 45 | #[error("InvalidAccountHeader")] 46 | InvalidAccountHeader = 616, 47 | #[error("InvalidNumberOfAccounts")] 48 | InvalidNumberOfAccounts = 617, 49 | #[error("InvalidReadableAccount")] 50 | InvalidReadableAccount = 618, 51 | #[error("PermissionViolation")] 52 | PermissionViolation = 619, 53 | #[error("NeedsSuccesfulAggregation")] 54 | NeedsSuccesfulAggregation = 620, 55 | #[error("MaxLastFeedIndexReached")] 56 | MaxLastFeedIndexReached = 621, 57 | #[error("FeedIndexAlreadyInitialized")] 58 | FeedIndexAlreadyInitialized = 622, 59 | #[error("NoNeedToResize")] 60 | NoNeedToResize = 623, 61 | } 62 | 63 | impl From for ProgramError { 64 | fn from(e: OracleError) -> Self { 65 | ProgramError::Custom(e as u32) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /pc/user.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace pc 10 | { 11 | 12 | class manager; 13 | 14 | // pyth daemon web-socket user connection 15 | class user : public prev_next, 16 | public net_connect, 17 | public ws_parser, 18 | public request_sub, 19 | public request_sub_i, 20 | public request_sub_i 21 | { 22 | public: 23 | user(); 24 | 25 | // associated rpc connection 26 | void set_rpc_client( rpc_client * ); 27 | 28 | // associated pyth server 29 | void set_manager( manager * ); 30 | 31 | // http request message parsing 32 | void parse_content( const char *, size_t ); 33 | 34 | // websocket message parsing 35 | void parse_msg( const char *buf, size_t sz ) override; 36 | 37 | // manager disconnected 38 | void teardown() override; 39 | 40 | // symbol update callback 41 | void on_response( price *, uint64_t ) override; 42 | 43 | // symbol price schedule callback 44 | void on_response( price_sched *, uint64_t ) override; 45 | 46 | private: 47 | 48 | // http-only request parsing 49 | struct user_http : public http_server { 50 | void parse_content( const char *, size_t ) override; 51 | user *ptr_; 52 | }; 53 | 54 | struct deferred_sub { 55 | price *sptr_; 56 | uint64_t sid_; 57 | }; 58 | 59 | typedef std::vector def_vec_t; 60 | 61 | void parse_request( uint32_t ); 62 | void parse_get_product_list( uint32_t ); 63 | void parse_get_product( uint32_t, uint32_t ); 64 | void parse_get_all_products( uint32_t ); 65 | void parse_upd_price( uint32_t, uint32_t ); 66 | void parse_sub_price( uint32_t, uint32_t ); 67 | void parse_sub_price_sched( uint32_t, uint32_t ); 68 | void add_header(); 69 | void add_tail( uint32_t id ); 70 | void add_parse_error(); 71 | void add_invalid_request( uint32_t id = 0 ); 72 | void add_invalid_params( uint32_t id ); 73 | void add_unknown_symbol( uint32_t id ); 74 | void add_error( uint32_t id, int err, str ); 75 | 76 | rpc_client *rptr_; // rpc manager api 77 | manager *sptr_; // manager collection 78 | user_http hsvr_; // http parser 79 | jtree jp_; // json parser 80 | json_wtr jw_; // json writer 81 | def_vec_t dvec_; // deferred subscriptions 82 | request_sub_set psub_; // price subscriptions 83 | }; 84 | 85 | } 86 | -------------------------------------------------------------------------------- /program/c/src/oracle/sort/test_sort_stable.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "../util/util.h" 3 | 4 | #define BEFORE(i,j) (((i)>>16)<((j)>>16)) 5 | 6 | #define SORT_NAME sort 7 | #define SORT_KEY_T int 8 | #define SORT_IDX_T int 9 | #define SORT_BEFORE(i,j) BEFORE(i,j) 10 | #include "tmpl/sort_stable.c" 11 | 12 | int test_sort_stable() { 13 | 14 | # define N 96 15 | int x[N]; 16 | int y[N]; 17 | int w[N]; 18 | 19 | /* Brute force validate small sizes via the 0-1 principle (with 20 | additional information in the keys to validate stability as well). */ 21 | 22 | for( int n=0; n<=24; n++ ) { 23 | for( long b=0L; b<(1L<>i) & 1L))<<16) | i; 25 | for( int i=0; i=n || z[i]!=w[j] ) { printf( "FAIL (corrupt)\n" ); return 1; } 33 | w[j] = -1; /* Mark that this entry has already been confirmed */ 34 | } 35 | for( int i=0; i=n || z[i]!=w[j] ) { printf( "FAIL (corrupt)\n" ); return 1; } 60 | w[j] = -1; /* Mark that this entry has already been confirmed */ 61 | } 62 | for( int i=0; i ProgramResult { 41 | let cmd_args = load::(instruction_data)?; 42 | 43 | pyth_assert( 44 | instruction_data.len() == size_of::() 45 | && cmd_args.publisher != Pubkey::default(), 46 | ProgramError::InvalidArgument, 47 | )?; 48 | 49 | let (funding_account, price_account, permissions_account) = match accounts { 50 | [x, y, p] => Ok((x, y, p)), 51 | _ => Err(OracleError::InvalidNumberOfAccounts), 52 | }?; 53 | 54 | check_valid_funding_account(funding_account)?; 55 | check_permissioned_funding_account( 56 | program_id, 57 | price_account, 58 | funding_account, 59 | permissions_account, 60 | &cmd_args.header, 61 | )?; 62 | 63 | let mut price_data = load_checked::(price_account, cmd_args.header.version)?; 64 | 65 | for i in 0..(try_convert::(price_data.num_)?) { 66 | if cmd_args.publisher == price_data.comp_[i].pub_ { 67 | for j in i + 1..(try_convert::(price_data.num_)?) { 68 | price_data.comp_[j - 1] = price_data.comp_[j]; 69 | } 70 | price_data.num_ -= 1; 71 | let current_index: usize = try_convert(price_data.num_)?; 72 | sol_memset( 73 | bytes_of_mut(&mut price_data.comp_[current_index]), 74 | 0, 75 | size_of::(), 76 | ); 77 | price_data.header.size = try_convert::<_, u32>(PriceAccount::INITIAL_SIZE)?; 78 | return Ok(()); 79 | } 80 | } 81 | Err(ProgramError::InvalidArgument) 82 | } 83 | -------------------------------------------------------------------------------- /program/rust/test_data/ema/1.result: -------------------------------------------------------------------------------- 1 | price,conf,expo,nslots,twap,twac 2 | 100,10,0,5000,100,10 3 | 147,48,0,1,108,16 4 | 191,74,0,1,116,22 5 | 235,99,0,1,124,27 6 | 100,10,0,1,114,20 7 | 147,48,0,1,117,22 8 | 191,13,0,1,133,20 9 | 235,22,0,1,145,20 10 | 100,10,0,1,136,18 11 | 147,15,0,1,137,18 12 | 191,74,0,1,138,19 13 | 235,99,0,1,140,20 14 | 235,99,0,1,142,22 15 | 235,99,0,1,143,23 16 | 235,99,0,1,145,24 17 | 235,99,0,1,146,25 18 | 235,99,0,1,148,27 19 | 235,99,0,1,149,28 20 | 235,99,0,1,150,29 21 | 235,99,0,1,152,30 22 | 235,99,0,1,153,31 23 | 235,99,0,20,154,32 24 | 235,99,0,1,155,33 25 | 235,99,0,1,156,34 26 | 235,99,0,1,158,35 27 | 235,99,0,1,159,36 28 | 235,99,0,1,160,37 29 | 235,99,0,1,161,37 30 | 235,99,0,1,162,38 31 | 235,99,0,40,163,39 32 | 235,99,0,1,164,40 33 | 235,99,0,1,165,41 34 | 235,99,0,1,165,41 35 | 235,99,0,1,166,42 36 | 235,99,0,1,167,43 37 | 235,99,0,1,168,43 38 | 235,99,0,1,169,44 39 | 235,99,0,1,170,45 40 | 235,15,0,1,174,43 41 | 235,99,0,1,175,43 42 | 235,20,0,1,178,42 43 | 235,99,0,1,179,43 44 | 235,50,0,1,180,43 45 | 235,99,0,1,180,43 46 | 235,99,0,1,181,44 47 | 235,99,0,1,182,44 48 | 235,99,0,1,182,45 49 | 235,33,0,1,184,45 50 | 235,33,0,1,185,44 51 | 235,33,0,1,186,44 52 | 235,33,0,1,188,44 53 | 235,33,0,1,189,43 54 | 235,99,0,1,189,44 55 | 235,99,0,1,190,44 56 | 235,99,0,1,190,45 57 | 235,99,0,1,190,45 58 | 235,99,0,1,191,46 59 | 235,99,0,1,191,46 60 | 235,99,0,1,191,46 61 | 235,99,0,1,192,47 62 | 235,99,0,1,192,47 63 | 235,99,0,1,192,48 64 | 235,99,0,1,193,48 65 | 235,99,0,1,193,48 66 | 235,99,0,1,193,49 67 | 235,99,0,1,194,49 68 | 235,99,0,1,194,50 69 | 235,99,0,1,194,50 70 | 235,99,0,1,195,50 71 | 235,99,0,1,195,51 72 | 235,99,0,1,195,51 73 | 235,99,0,1,195,51 74 | 235,99,0,1,196,52 75 | 235,99,0,1,196,52 76 | 235,99,0,1,196,52 77 | 235,99,0,1,197,53 78 | 235,99,0,1,197,53 79 | 235,99,0,1,197,53 80 | 235,99,0,1,197,54 81 | 235,99,0,1,198,54 82 | 235,99,0,1,198,54 83 | 235,99,0,1,198,55 84 | 235,99,0,1,198,55 85 | 235,99,0,1,199,55 86 | 235,99,0,1,199,55 87 | 235,99,0,1,199,56 88 | 235,99,0,1,199,56 89 | 235,99,0,1,200,56 90 | 235,99,0,1,200,57 91 | 235,99,0,1,200,57 92 | 235,99,0,1,200,57 93 | 235,99,0,1,200,57 94 | 235,99,0,1,201,58 95 | 235,99,0,1,201,58 96 | 235,99,0,1,201,58 97 | 235,99,0,1,201,58 98 | 235,99,0,1,202,59 99 | 235,99,0,1,202,59 100 | 235,99,0,1,202,59 101 | 235,99,0,1,202,59 102 | 180,5,0,1,200,53 103 | 180,5,0,1,198,49 104 | 180,5,0,1,196,45 105 | 180,5,0,1,195,42 106 | 180,5,0,1,194,39 107 | 180,5,0,1,193,36 108 | 180,5,0,1,192,34 109 | 180,5,0,1,191,32 110 | 180,5,0,1,190,31 111 | 180,5,0,1,190,29 112 | 180,5,0,1,189,28 113 | 180,5,0,1,189,27 114 | 180,5,0,1,188,26 115 | 180,5,0,1,188,25 116 | 180,5,0,1,188,24 117 | 180,5,0,1,187,23 118 | 180,5,0,1,187,22 119 | 180,5,0,1,187,22 120 | 180,5,0,1,186,21 121 | 180,5,0,1,186,21 122 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG SOLANA_VERSION=v1.14.7 2 | ARG SOLANA_DOCKER_IMAGE_HASH=9130b4a26ec00c4ba99c023da98c06abb0dba2904c990af7770928e0f7dfd2d5 3 | 4 | FROM solanalabs/solana:v${SOLANA_VERSION}@sha256:${SOLANA_DOCKER_IMAGE_HASH} 5 | 6 | # Redeclare SOLANA_VERSION in the new build stage. 7 | # Persist in env for docker run & inspect. 8 | ENV SOLANA_VERSION="${SOLANA_VERSION}" 9 | 10 | RUN apt-get update 11 | RUN apt-get install -qq \ 12 | cmake \ 13 | curl \ 14 | g++ \ 15 | gcc-multilib \ 16 | git \ 17 | libzstd1 \ 18 | libzstd-dev \ 19 | sudo \ 20 | zlib1g \ 21 | zlib1g-dev \ 22 | qtbase5-dev \ 23 | qtchooser \ 24 | qt5-qmake \ 25 | qtbase5-dev-tools \ 26 | libqt5websockets5-dev\ 27 | libclang-dev 28 | 29 | # Install jcon-cpp library 30 | RUN git clone https://github.com/joncol/jcon-cpp.git /jcon-cpp && cd /jcon-cpp && git checkout 2235654e39c7af505d7158bf996e47e37a23d6e3 && mkdir build && cd build && cmake .. && make -j4 && make install 31 | 32 | # Grant sudo access to pyth user 33 | RUN echo "pyth ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers 34 | RUN useradd -m pyth 35 | 36 | # Fixes a bug in the solana docker image 37 | # https://github.com/solana-labs/solana/issues/20577 38 | RUN mkdir /usr/bin/sdk/bpf/dependencies \ 39 | && chmod -R 777 /usr/bin/sdk/bpf 40 | 41 | 42 | USER pyth 43 | WORKDIR /home/pyth 44 | 45 | #Install rust 46 | RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \ 47 | | sh -s -- -y 48 | 49 | ENV PATH=${PATH}:/home/pyth/.cargo/bin 50 | 51 | COPY --chown=pyth:pyth ./scripts/get-bpf-tools.sh pyth-client/scripts/ 52 | RUN ./pyth-client/scripts/get-bpf-tools.sh 53 | 54 | # Apply patch to solana 55 | COPY --chown=pyth:pyth ./scripts/patch-solana.sh ./scripts/solana.patch pyth-client/scripts/ 56 | RUN ./pyth-client/scripts/patch-solana.sh 57 | 58 | # Copy C/C++ sources 59 | COPY --chown=pyth:pyth ./pc pyth-client/pc 60 | COPY --chown=pyth:pyth ./pctest pyth-client/pctest 61 | COPY --chown=pyth:pyth ./pcapps pyth-client/pcapps 62 | COPY --chown=pyth:pyth ./program/c pyth-client/program/c 63 | COPY --chown=pyth:pyth ./CMakeLists.txt pyth-client 64 | COPY --chown=pyth:pyth ./scripts/build.sh pyth-client/scripts/ 65 | 66 | # Build C/C++ offchain binaries. 67 | RUN cd pyth-client && ./scripts/build.sh 68 | 69 | # layer-cache crates.io packages from Cargo.{toml|lock} and rustup toolchain 70 | COPY --chown=pyth:pyth program/rust/Cargo.toml pyth-client/program/rust/ 71 | COPY --chown=pyth:pyth Cargo.toml Cargo.lock rust-toolchain pyth-client/ 72 | RUN mkdir -p pyth-client/program/rust/src && touch pyth-client/program/rust/src/lib.rs 73 | RUN cd pyth-client && cargo fetch --locked 74 | 75 | # Do final source code copy to overwrite the placeholder lib.rs 76 | COPY --chown=pyth:pyth ./ pyth-client/ 77 | 78 | # Build and test the oracle program. 79 | RUN cd pyth-client && ./scripts/build-bpf.sh . 80 | 81 | ENTRYPOINT [] 82 | CMD [] 83 | -------------------------------------------------------------------------------- /pc/dbl_list.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace pc 4 | { 5 | 6 | // doubly-linked list implementing prev_next 7 | template 8 | class dbl_list 9 | { 10 | public: 11 | dbl_list(); 12 | void clear(); 13 | bool empty() const; 14 | void add( T * ); 15 | void del( T * ); 16 | T *first() const; 17 | T *last() const; 18 | private: 19 | T *hd_; 20 | T *tl_; 21 | }; 22 | 23 | // node for single-linked list 24 | template 25 | class next 26 | { 27 | public: 28 | next(); 29 | 30 | void set_next( T * ); 31 | T *get_next() const; 32 | 33 | private: 34 | T *next_; 35 | }; 36 | 37 | // node for doubly-linked list 38 | template 39 | class prev_next : public next 40 | { 41 | public: 42 | prev_next(); 43 | 44 | void set_prev( T * ); 45 | T *get_prev() const; 46 | 47 | private: 48 | T *prev_; 49 | }; 50 | 51 | 52 | template 53 | dbl_list::dbl_list() 54 | : hd_( nullptr ), 55 | tl_( nullptr ) 56 | { 57 | } 58 | 59 | template 60 | void dbl_list::clear() 61 | { 62 | hd_ = tl_ = nullptr; 63 | } 64 | 65 | template 66 | bool dbl_list::empty() const 67 | { 68 | return hd_ == nullptr; 69 | } 70 | 71 | template 72 | void dbl_list::add( T *n ) 73 | { 74 | n->set_prev( tl_ ); 75 | n->set_next( nullptr ); 76 | if ( tl_ ) { 77 | tl_->set_next( n ); 78 | } else { 79 | hd_ = n; 80 | } 81 | tl_ = n; 82 | } 83 | 84 | template 85 | void dbl_list::del( T *t ) 86 | { 87 | T *p = t->get_prev(); 88 | T *n = t->get_next(); 89 | if ( p ) { 90 | p->set_next( n ); 91 | } else { 92 | hd_ = n; 93 | } 94 | if ( n ) { 95 | n->set_prev( p ); 96 | } else { 97 | tl_ = p; 98 | } 99 | } 100 | 101 | template 102 | T *dbl_list::first() const 103 | { 104 | return hd_; 105 | } 106 | 107 | template 108 | T *dbl_list::last() const 109 | { 110 | return tl_; 111 | } 112 | 113 | template 114 | next::next() 115 | : next_( nullptr ) 116 | { 117 | } 118 | 119 | template 120 | void next::set_next( T *next ) 121 | { 122 | next_ = next; 123 | } 124 | 125 | template 126 | T *next::get_next() const 127 | { 128 | return next_; 129 | } 130 | 131 | template 132 | prev_next::prev_next() 133 | : prev_( nullptr ) 134 | { 135 | } 136 | 137 | template 138 | void prev_next::set_prev( T *prev ) 139 | { 140 | prev_= prev; 141 | } 142 | 143 | template 144 | T *prev_next::get_prev() const 145 | { 146 | return prev_; 147 | } 148 | 149 | 150 | } 151 | -------------------------------------------------------------------------------- /program/rust/src/accounts/permission.rs: -------------------------------------------------------------------------------- 1 | use { 2 | super::{ 3 | AccountHeader, 4 | PythAccount, 5 | }, 6 | crate::{ 7 | c_oracle_header::PC_ACCTYPE_PERMISSIONS, 8 | instruction::OracleCommand, 9 | }, 10 | bytemuck::{ 11 | Pod, 12 | Zeroable, 13 | }, 14 | solana_program::{ 15 | account_info::AccountInfo, 16 | program_error::ProgramError, 17 | pubkey::Pubkey, 18 | }, 19 | std::{ 20 | cell::RefMut, 21 | mem::size_of, 22 | }, 23 | }; 24 | 25 | /// This account stores the pubkeys that can execute administrative instructions in the Pyth 26 | /// program. Only the upgrade authority of the program can update these permissions. 27 | #[repr(C)] 28 | #[derive(Copy, Clone, Pod, Zeroable)] 29 | pub struct PermissionAccount { 30 | /// pyth account header 31 | pub header: AccountHeader, 32 | /// An authority that can do any administrative task 33 | pub master_authority: Pubkey, 34 | /// An authority that can : 35 | /// - Add mapping accounts 36 | /// - Add price accounts 37 | /// - Add product accounts 38 | /// - Delete price accounts 39 | /// - Delete product accounts 40 | /// - Update product accounts 41 | pub data_curation_authority: Pubkey, 42 | /// An authority that can : 43 | /// - Add publishers 44 | /// - Delete publishers 45 | /// - Set minimum number of publishers 46 | pub security_authority: Pubkey, 47 | } 48 | 49 | impl PermissionAccount { 50 | pub fn is_authorized(&self, key: &Pubkey, command: OracleCommand) -> bool { 51 | #[allow(clippy::match_like_matches_macro)] 52 | match (*key, command) { 53 | (pubkey, _) if pubkey == self.master_authority => true, 54 | (pubkey, OracleCommand::ResizePriceAccount) if pubkey == self.security_authority => { 55 | true 56 | } // Allow for an admin key to resize the price account 57 | _ => false, 58 | } 59 | } 60 | 61 | pub fn load_last_feed_index_mut<'a>( 62 | account: &'a AccountInfo, 63 | ) -> Result, ProgramError> { 64 | let start = size_of::(); 65 | let end = start + size_of::(); 66 | assert_eq!(Self::NEW_ACCOUNT_SPACE, end); 67 | if account.data_len() < end { 68 | return Err(ProgramError::AccountDataTooSmall); 69 | } 70 | Ok(RefMut::map(account.try_borrow_mut_data()?, |data| { 71 | bytemuck::from_bytes_mut(&mut data[start..end]) 72 | })) 73 | } 74 | } 75 | 76 | impl PythAccount for PermissionAccount { 77 | const ACCOUNT_TYPE: u32 = PC_ACCTYPE_PERMISSIONS; 78 | const NEW_ACCOUNT_SPACE: usize = size_of::() + size_of::(); 79 | const INITIAL_SIZE: u32 = Self::NEW_ACCOUNT_SPACE as u32; 80 | } 81 | -------------------------------------------------------------------------------- /DEPEND.md: -------------------------------------------------------------------------------- 1 | # Open source dependencies 2 | 3 | ## Base-64 Encoding/Decoding 4 | 5 | The pyth-client project contains source code from Adam Rudd's arduino-base64 project for base64 encoding/decoding maintained at https://github.com/adamvr/arduino-base64. The corresponding copyright and permission notice are reproduced below: 6 | 7 | Copyright (C) 2013 Adam Rudd 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 14 | 15 | 16 | ## Base-58 Encoding/Decoding 17 | 18 | The pyth-client project contains source code for base-58 encoding/decoding found at: https://bitcoin.stackexchange.com/questions/76480/encode-decode-base-58-c and refers to or derives from the github project maintained at: https://github.com/cryptocoinjs/base-x. The corresponding copyright and permission notice are reproduced below: 19 | 20 | 21 | The MIT License (MIT) 22 | 23 | Copyright (c) 2018 base-x contributors Copyright (c) 2014-2018 The Bitcoin Core developers 24 | 25 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 26 | 27 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 28 | 29 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 30 | -------------------------------------------------------------------------------- /program/rust/src/processor/upd_permissions.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::{ 3 | accounts::{ 4 | PermissionAccount, 5 | PythAccount, 6 | PERMISSIONS_SEED, 7 | }, 8 | deserialize::{ 9 | load, 10 | load_checked, 11 | }, 12 | instruction::UpdPermissionsArgs, 13 | utils::{ 14 | check_is_upgrade_authority_for_program, 15 | check_valid_funding_account, 16 | check_valid_writable_account, 17 | pyth_assert, 18 | }, 19 | OracleError, 20 | }, 21 | solana_program::{ 22 | account_info::AccountInfo, 23 | entrypoint::ProgramResult, 24 | pubkey::Pubkey, 25 | system_program::check_id, 26 | }, 27 | }; 28 | 29 | /// Updates permissions for the pyth oracle program 30 | /// This function can create and update the permissions accounts, which stores 31 | /// several public keys that can execute administrative instructions in the pyth program 32 | // account[0] upgrade authority [signer writable] 33 | // account[1] programdata account [] 34 | // account[2] permissions account [writable] 35 | // account[3] system program [] 36 | pub fn upd_permissions( 37 | program_id: &Pubkey, 38 | accounts: &[AccountInfo], 39 | instruction_data: &[u8], 40 | ) -> ProgramResult { 41 | let [funding_account, programdata_account, permissions_account, system_program] = match accounts 42 | { 43 | [w, x, y, z] => Ok([w, x, y, z]), 44 | _ => Err(OracleError::InvalidNumberOfAccounts), 45 | }?; 46 | 47 | let cmd_args = load::(instruction_data)?; 48 | 49 | check_valid_funding_account(funding_account)?; 50 | check_is_upgrade_authority_for_program(funding_account, programdata_account, program_id)?; 51 | 52 | let (permission_pda_address, bump_seed) = 53 | Pubkey::find_program_address(&[PERMISSIONS_SEED.as_bytes()], program_id); 54 | pyth_assert( 55 | permission_pda_address == *permissions_account.key, 56 | OracleError::InvalidPda.into(), 57 | )?; 58 | 59 | pyth_assert( 60 | check_id(system_program.key), 61 | OracleError::InvalidSystemAccount.into(), 62 | )?; 63 | 64 | 65 | // Create PermissionAccount if it doesn't exist 66 | PermissionAccount::initialize_pda( 67 | permissions_account, 68 | funding_account, 69 | system_program, 70 | program_id, 71 | &[PERMISSIONS_SEED.as_bytes(), &[bump_seed]], 72 | cmd_args.header.version, 73 | )?; 74 | 75 | check_valid_writable_account(program_id, permissions_account)?; 76 | 77 | let mut permissions_account_data = 78 | load_checked::(permissions_account, cmd_args.header.version)?; 79 | permissions_account_data.master_authority = cmd_args.master_authority; 80 | 81 | permissions_account_data.data_curation_authority = cmd_args.data_curation_authority; 82 | permissions_account_data.security_authority = cmd_args.security_authority; 83 | 84 | Ok(()) 85 | } 86 | -------------------------------------------------------------------------------- /program/rust/src/processor/add_product.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::{ 3 | accounts::{ 4 | update_product_metadata, 5 | MappingAccount, 6 | ProductAccount, 7 | PythAccount, 8 | }, 9 | c_oracle_header::PC_MAP_TABLE_SIZE, 10 | deserialize::{ 11 | load, 12 | load_checked, 13 | }, 14 | instruction::CommandHeader, 15 | utils::{ 16 | check_permissioned_funding_account, 17 | check_valid_funding_account, 18 | pyth_assert, 19 | try_convert, 20 | }, 21 | OracleError, 22 | }, 23 | solana_program::{ 24 | account_info::AccountInfo, 25 | entrypoint::ProgramResult, 26 | program_error::ProgramError, 27 | pubkey::Pubkey, 28 | }, 29 | std::mem::{ 30 | size_of, 31 | size_of_val, 32 | }, 33 | }; 34 | 35 | /// Initialize and add new product reference data account 36 | // account[0] funding account [signer writable] 37 | // account[1] mapping account [signer writable] 38 | // account[2] new product account [signer writable] 39 | // account[3] permissions account [] 40 | pub fn add_product( 41 | program_id: &Pubkey, 42 | accounts: &[AccountInfo], 43 | instruction_data: &[u8], 44 | ) -> ProgramResult { 45 | let (funding_account, tail_mapping_account, new_product_account, permissions_account) = 46 | match accounts { 47 | [x, y, z, p] => Ok((x, y, z, p)), 48 | _ => Err(OracleError::InvalidNumberOfAccounts), 49 | }?; 50 | 51 | let hdr = load::(instruction_data)?; 52 | 53 | check_valid_funding_account(funding_account)?; 54 | check_permissioned_funding_account( 55 | program_id, 56 | tail_mapping_account, 57 | funding_account, 58 | permissions_account, 59 | hdr, 60 | )?; 61 | check_permissioned_funding_account( 62 | program_id, 63 | new_product_account, 64 | funding_account, 65 | permissions_account, 66 | hdr, 67 | )?; 68 | 69 | 70 | let mut mapping_data = load_checked::(tail_mapping_account, hdr.version)?; 71 | // The mapping account must have free space to add the product account 72 | pyth_assert( 73 | mapping_data.number_of_products < PC_MAP_TABLE_SIZE, 74 | ProgramError::InvalidArgument, 75 | )?; 76 | 77 | ProductAccount::initialize(new_product_account, hdr.version)?; 78 | 79 | let current_index: usize = try_convert(mapping_data.number_of_products)?; 80 | mapping_data.products_list[current_index] = *new_product_account.key; 81 | mapping_data.number_of_products += 1; 82 | mapping_data.header.size = try_convert::<_, u32>( 83 | size_of::() - size_of_val(&mapping_data.products_list), 84 | )? + mapping_data.number_of_products 85 | * try_convert::<_, u32>(size_of::())?; 86 | 87 | update_product_metadata(instruction_data, new_product_account, hdr.version)?; 88 | 89 | Ok(()) 90 | } 91 | -------------------------------------------------------------------------------- /pc/misc.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #define PC_PACKED __attribute__((__packed__)) 7 | #define PC_LIKELY(ARG) __builtin_expect((ARG),1) 8 | #define PC_UNLIKELY(ARG) __builtin_expect((ARG),0) 9 | #define PC_NSECS_IN_SEC 1000000000L 10 | #define PC_NSECS_IN_MSEC 1000000L 11 | 12 | namespace pc 13 | { 14 | 15 | // base58 encoding from base-x conversion impl. 16 | int enc_base58( const uint8_t *src, int len, char *result, int rlen); 17 | int dec_base58( const uint8_t *str, int len, uint8_t *result ); 18 | 19 | // base64 encoding courtesy of 20 | // Adam Rudd per licence: github.com/adamvr/arduino-base64 21 | size_t enc_base64_len( size_t len ); 22 | size_t enc_base64( const uint8_t *src, int len, char *result ); 23 | size_t dec_base64( const char *str, int len, uint8_t *result ); 24 | 25 | // integer to string encoding 26 | char *uint_to_str( uint64_t val, char *end_ptr ); 27 | uint64_t str_to_uint( const char *str, unsigned len ); 28 | char *int_to_str( int64_t val, char *end_ptr ); 29 | int64_t str_to_int( const char *str, unsigned len ); 30 | 31 | // string representation of decimal to integer with implied decimal places 32 | int64_t str_to_dec( const char *str, int len, int expo ); 33 | int64_t str_to_dec( const char *str, int expo ); 34 | 35 | // current time 36 | int64_t get_now(); 37 | char *nsecs_to_utc6( int64_t ts, char *cptr ); 38 | 39 | // get host/port from [:port1[:port2]] convention 40 | std::string get_host_port( const std::string& host, int&port1, int&port2); 41 | 42 | // string as char pointer plus length 43 | struct str 44 | { 45 | str(); 46 | str( const char * ); 47 | str( const char *, size_t ); 48 | str( const uint8_t *, size_t ); 49 | str( const std::string& ); 50 | std::string as_string() const; 51 | bool operator==( const str& ) const; 52 | bool operator!=( const str& ) const; 53 | const char *str_; 54 | size_t len_; 55 | }; 56 | 57 | ///////////////////////////////////////////////////////////////////////// 58 | // inline impl 59 | 60 | inline str::str() 61 | : str_( nullptr ), len_( 0 ) { 62 | } 63 | 64 | inline str::str( const char *str ) 65 | : str_( str ), len_( __builtin_strlen( str ) ) { 66 | } 67 | 68 | inline str::str( const char *str, size_t len ) 69 | : str_( str ), len_( len ) { 70 | } 71 | 72 | inline str::str( const uint8_t *str, size_t len ) 73 | : str_( (const char*)str ), len_( len ) { 74 | } 75 | 76 | inline str::str( const std::string& str ) 77 | : str_( str.c_str() ), len_( str.length() ) { 78 | } 79 | 80 | inline bool str::operator==( const str& obj ) const 81 | { 82 | return len_ == obj.len_ && 83 | 0 == __builtin_strncmp( str_, obj.str_, len_); 84 | } 85 | 86 | inline bool str::operator!=( const str& obj ) const 87 | { 88 | return len_ != obj.len_ || 89 | 0 != __builtin_strncmp( str_, obj.str_, len_); 90 | } 91 | 92 | inline std::string str::as_string() const 93 | { 94 | return std::string( str_, len_ ); 95 | } 96 | 97 | } 98 | -------------------------------------------------------------------------------- /program/rust/src/deserialize.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::{ 3 | accounts::{ 4 | AccountHeader, 5 | PythAccount, 6 | }, 7 | c_oracle_header::PC_MAGIC, 8 | error::OracleError, 9 | utils::pyth_assert, 10 | }, 11 | bytemuck::{ 12 | try_from_bytes, 13 | try_from_bytes_mut, 14 | Pod, 15 | }, 16 | solana_program::{ 17 | account_info::AccountInfo, 18 | program_error::ProgramError, 19 | }, 20 | std::{ 21 | cell::{ 22 | Ref, 23 | RefMut, 24 | }, 25 | mem::size_of, 26 | }, 27 | }; 28 | 29 | /// Interpret the bytes in `data` as a value of type `T` 30 | /// This will fail if : 31 | /// - `data` is too short 32 | /// - `data` is not aligned for T 33 | pub fn load(data: &[u8]) -> Result<&T, OracleError> { 34 | try_from_bytes( 35 | data.get(0..size_of::()) 36 | .ok_or(OracleError::InstructionDataTooShort)?, 37 | ) 38 | .map_err(|_| OracleError::InstructionDataSliceMisaligned) 39 | } 40 | 41 | /// Interpret the bytes in `data` as a mutable value of type `T` 42 | #[allow(unused)] 43 | pub fn load_mut(data: &mut [u8]) -> Result<&mut T, OracleError> { 44 | try_from_bytes_mut( 45 | data.get_mut(0..size_of::()) 46 | .ok_or(OracleError::InstructionDataTooShort)?, 47 | ) 48 | .map_err(|_| OracleError::InstructionDataSliceMisaligned) 49 | } 50 | 51 | /// Get the data stored in `account` as a value of type `T`. 52 | /// WARNING : Use `load_checked` to load initialized Pyth accounts 53 | pub fn load_account_as<'a, T: Pod>(account: &'a AccountInfo) -> Result, ProgramError> { 54 | let data = account.try_borrow_data()?; 55 | 56 | Ok(Ref::map(data, |data| { 57 | bytemuck::from_bytes(&data[0..size_of::()]) 58 | })) 59 | } 60 | 61 | /// Mutably borrow the data in `account` as a value of type `T`. 62 | /// Any mutations to the returned value will be reflected in the account data. 63 | /// WARNING : Use `load_checked` to load initialized Pyth accounts 64 | pub fn load_account_as_mut<'a, T: Pod>( 65 | account: &'a AccountInfo, 66 | ) -> Result, ProgramError> { 67 | let data = account.try_borrow_mut_data()?; 68 | 69 | Ok(RefMut::map(data, |data| { 70 | bytemuck::from_bytes_mut(&mut data[0..size_of::()]) 71 | })) 72 | } 73 | 74 | pub fn load_checked<'a, T: PythAccount>( 75 | account: &'a AccountInfo, 76 | version: u32, 77 | ) -> Result, ProgramError> { 78 | pyth_assert( 79 | account.data_len() >= T::MINIMUM_SIZE, 80 | OracleError::AccountTooSmall.into(), 81 | )?; 82 | 83 | { 84 | let account_header = load_account_as::(account)?; 85 | pyth_assert( 86 | account_header.magic_number == PC_MAGIC 87 | && account_header.version == version 88 | && account_header.account_type == T::ACCOUNT_TYPE, 89 | OracleError::InvalidAccountHeader.into(), 90 | )?; 91 | } 92 | 93 | load_account_as_mut::(account) 94 | } 95 | -------------------------------------------------------------------------------- /program/rust/src/tests/test_set_min_pub.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::{ 3 | accounts::{ 4 | PermissionAccount, 5 | PriceAccount, 6 | PythAccount, 7 | }, 8 | c_oracle_header::PC_VERSION, 9 | deserialize::{ 10 | load_checked, 11 | load_mut, 12 | }, 13 | instruction::{ 14 | OracleCommand, 15 | SetMinPubArgs, 16 | }, 17 | processor::process_instruction, 18 | tests::test_utils::AccountSetup, 19 | }, 20 | solana_program::{ 21 | account_info::AccountInfo, 22 | program_error::ProgramError, 23 | pubkey::Pubkey, 24 | }, 25 | std::mem::size_of, 26 | }; 27 | 28 | #[test] 29 | fn test_set_min_pub() { 30 | let mut instruction_data = [0u8; size_of::()]; 31 | 32 | let program_id = Pubkey::new_unique(); 33 | 34 | let mut funding_setup = AccountSetup::new_funding(); 35 | let funding_account = funding_setup.as_account_info(); 36 | 37 | let mut price_setup = AccountSetup::new::(&program_id); 38 | let price_account = price_setup.as_account_info(); 39 | PriceAccount::initialize(&price_account, PC_VERSION).unwrap(); 40 | 41 | let mut permissions_setup = AccountSetup::new_permission(&program_id); 42 | let permissions_account = permissions_setup.as_account_info(); 43 | 44 | { 45 | let mut permissions_account_data = 46 | PermissionAccount::initialize(&permissions_account, PC_VERSION).unwrap(); 47 | permissions_account_data.master_authority = *funding_account.key; 48 | permissions_account_data.data_curation_authority = *funding_account.key; 49 | permissions_account_data.security_authority = *funding_account.key; 50 | } 51 | 52 | assert_eq!(get_min_pub(&price_account), Ok(0)); 53 | 54 | populate_instruction(&mut instruction_data, 10); 55 | assert!(process_instruction( 56 | &program_id, 57 | &[ 58 | funding_account.clone(), 59 | price_account.clone(), 60 | permissions_account.clone() 61 | ], 62 | &instruction_data 63 | ) 64 | .is_ok()); 65 | assert_eq!(get_min_pub(&price_account), Ok(10)); 66 | 67 | populate_instruction(&mut instruction_data, 2); 68 | assert!(process_instruction( 69 | &program_id, 70 | &[ 71 | funding_account.clone(), 72 | price_account.clone(), 73 | permissions_account.clone() 74 | ], 75 | &instruction_data 76 | ) 77 | .is_ok()); 78 | assert_eq!(get_min_pub(&price_account), Ok(2)); 79 | } 80 | 81 | // Create an upd_product instruction that sets the product metadata to strings 82 | fn populate_instruction(instruction_data: &mut [u8], min_pub: u8) { 83 | let mut hdr = load_mut::(instruction_data).unwrap(); 84 | hdr.header = OracleCommand::SetMinPub.into(); 85 | hdr.minimum_publishers = min_pub; 86 | } 87 | 88 | fn get_min_pub(account: &AccountInfo) -> Result { 89 | Ok(load_checked::(account, PC_VERSION)?.min_pub_) 90 | } 91 | -------------------------------------------------------------------------------- /pc/hash_map.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace pc 7 | { 8 | 9 | // hash map based on type trait T 10 | template 11 | class hash_map 12 | { 13 | public: 14 | 15 | typedef typename T::idx_t idx_t; 16 | typedef typename T::key_t key_t; 17 | typedef typename T::keyref_t keyref_t; 18 | typedef typename T::val_t val_t; 19 | typedef typename T::hash_t hash_t; 20 | typedef idx_t *iter_t; 21 | static const size_t hsize_ = T::hsize_; 22 | 23 | hash_map( hash_t hfn = hash_t() ); 24 | 25 | iter_t find( keyref_t ); 26 | iter_t add( keyref_t ); 27 | const val_t &const_ref( iter_t ) const; 28 | val_t &ref( iter_t ); 29 | val_t obj( iter_t ); 30 | void del( iter_t ); 31 | size_t size() const; 32 | void clear(); 33 | 34 | private: 35 | 36 | struct node { 37 | idx_t nxt_; 38 | key_t key_; 39 | val_t val_; 40 | }; 41 | 42 | typedef std::vector node_vec_t; 43 | 44 | node_vec_t nvec_; 45 | idx_t htab_[hsize_]; 46 | idx_t rhd_; 47 | hash_t hfn_; 48 | size_t nval_; 49 | }; 50 | 51 | template 52 | hash_map::hash_map( hash_t hfn ) 53 | : nvec_( 1 ), 54 | rhd_( 0 ), 55 | hfn_( hfn ), 56 | nval_( 0 ) 57 | { 58 | __builtin_memset( htab_, 0 , sizeof( htab_ ) ); 59 | } 60 | 61 | template 62 | typename hash_map::iter_t hash_map::find( keyref_t k ) 63 | { 64 | idx_t i = hfn_( k )%hsize_; 65 | for( iter_t p = &htab_[i]; *p; ) { 66 | node& nd = nvec_[*p]; 67 | if ( k == nd.key_ ) { 68 | return p; 69 | } 70 | p = &nvec_[*p].nxt_; 71 | } 72 | return nullptr; 73 | } 74 | 75 | template 76 | void hash_map::del( iter_t i ) 77 | { 78 | idx_t it = *i; 79 | node& nd = nvec_[it]; 80 | *i = nd.nxt_; 81 | nd.nxt_ = rhd_; 82 | rhd_ = it; 83 | --nval_; 84 | } 85 | 86 | template 87 | typename hash_map::iter_t hash_map::add( keyref_t k ) 88 | { 89 | idx_t res = rhd_; 90 | if ( res ) { 91 | rhd_ = nvec_[rhd_].nxt_; 92 | } else { 93 | res = nvec_.size(); 94 | nvec_.resize( 1 + res ); 95 | } 96 | idx_t i = hfn_( k )%hsize_; 97 | idx_t n = htab_[i]; 98 | htab_[i] = res; 99 | node& nd = nvec_[res]; 100 | nd.nxt_ = n; 101 | nd.key_ = k; 102 | ++nval_; 103 | return &htab_[i]; 104 | } 105 | 106 | template 107 | typename T::val_t &hash_map::ref( iter_t i ) 108 | { 109 | return nvec_[*i].val_; 110 | } 111 | 112 | template 113 | typename T::val_t hash_map::obj( iter_t i ) 114 | { 115 | return nvec_[*i].val_; 116 | } 117 | 118 | template 119 | size_t hash_map::size() const 120 | { 121 | return nval_; 122 | } 123 | 124 | template 125 | void hash_map::clear() 126 | { 127 | nvec_.resize( 1 ); 128 | rhd_ = 0; 129 | nval_ = 0; 130 | __builtin_memset( htab_, 0 , sizeof( htab_ ) ); 131 | } 132 | 133 | } 134 | -------------------------------------------------------------------------------- /program/rust/src/tests/test_set_max_latency.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::{ 3 | accounts::{ 4 | PermissionAccount, 5 | PriceAccount, 6 | PythAccount, 7 | }, 8 | c_oracle_header::PC_VERSION, 9 | deserialize::{ 10 | load_checked, 11 | load_mut, 12 | }, 13 | instruction::{ 14 | OracleCommand, 15 | SetMaxLatencyArgs, 16 | }, 17 | processor::set_max_latency, 18 | tests::test_utils::AccountSetup, 19 | }, 20 | solana_program::{ 21 | account_info::AccountInfo, 22 | program_error::ProgramError, 23 | pubkey::Pubkey, 24 | }, 25 | std::mem::size_of, 26 | }; 27 | 28 | #[test] 29 | fn test_set_max_latency() { 30 | let mut instruction_data = [0u8; size_of::()]; 31 | 32 | let program_id = Pubkey::new_unique(); 33 | 34 | let mut funding_setup = AccountSetup::new_funding(); 35 | let funding_account = funding_setup.as_account_info(); 36 | 37 | let mut price_setup = AccountSetup::new::(&program_id); 38 | let price_account = price_setup.as_account_info(); 39 | PriceAccount::initialize(&price_account, PC_VERSION).unwrap(); 40 | 41 | let mut permissions_setup = AccountSetup::new_permission(&program_id); 42 | let permissions_account = permissions_setup.as_account_info(); 43 | 44 | { 45 | let mut permissions_account_data = 46 | PermissionAccount::initialize(&permissions_account, PC_VERSION).unwrap(); 47 | permissions_account_data.master_authority = *funding_account.key; 48 | permissions_account_data.data_curation_authority = *funding_account.key; 49 | permissions_account_data.security_authority = *funding_account.key; 50 | } 51 | 52 | assert_eq!(get_max_latency(&price_account), Ok(0)); 53 | 54 | populate_instruction(&mut instruction_data, 10); 55 | assert!(set_max_latency( 56 | &program_id, 57 | &[ 58 | funding_account.clone(), 59 | price_account.clone(), 60 | permissions_account.clone() 61 | ], 62 | &instruction_data 63 | ) 64 | .is_ok()); 65 | assert_eq!(get_max_latency(&price_account), Ok(10)); 66 | 67 | populate_instruction(&mut instruction_data, 5); 68 | assert!(set_max_latency( 69 | &program_id, 70 | &[ 71 | funding_account.clone(), 72 | price_account.clone(), 73 | permissions_account.clone() 74 | ], 75 | &instruction_data 76 | ) 77 | .is_ok()); 78 | assert_eq!(get_max_latency(&price_account), Ok(5)); 79 | } 80 | 81 | // Populate the instruction data with SetMaxLatencyArgs 82 | fn populate_instruction(instruction_data: &mut [u8], max_latency: u8) { 83 | let mut hdr = load_mut::(instruction_data).unwrap(); 84 | hdr.header = OracleCommand::SetMaxLatency.into(); 85 | hdr.max_latency = max_latency; 86 | } 87 | 88 | // Helper function to get the max latency from a PriceAccount 89 | fn get_max_latency(account: &AccountInfo) -> Result { 90 | Ok(load_checked::(account, PC_VERSION)?.max_latency_) 91 | } 92 | -------------------------------------------------------------------------------- /program/rust/test_data/ema/price10_conf2.csv: -------------------------------------------------------------------------------- 1 | price,conf,expo,nslots 2 | 1234567890,10,0,5000 3 | 1234567890,10,0,1 4 | 1234567890,10,0,1 5 | 1234567890,10,0,1 6 | 1234567890,10,0,1 7 | 1234567890,10,0,1 8 | 1234567890,10,0,1 9 | 1234567890,10,0,1 10 | 1234567890,10,0,1 11 | 1234567890,10,0,1 12 | 1234567890,10,0,1 13 | 1234567890,10,0,1 14 | 1234567890,10,0,1 15 | 1234567890,10,0,1 16 | 1234567890,10,0,1 17 | 1234567890,10,0,1 18 | 1234567890,10,0,1 19 | 1234567890,10,0,1 20 | 1234567890,10,0,1 21 | 1234567890,10,0,1 22 | 1234567890,10,0,1 23 | 1234567890,10,0,1 24 | 1234567890,10,0,1 25 | 1234567890,10,0,1 26 | 1234567890,10,0,1 27 | 1234567890,10,0,1 28 | 1234567890,10,0,1 29 | 1234567890,10,0,1 30 | 1234567890,10,0,1 31 | 1234567890,10,0,1 32 | 1234567890,10,0,1 33 | 1234567890,10,0,1 34 | 1234567890,10,0,1 35 | 1234567890,10,0,1 36 | 1234567890,10,0,1 37 | 1234567890,10,0,1 38 | 1234567890,10,0,1 39 | 1234567890,10,0,1 40 | 1234567890,10,0,1 41 | 1234567890,10,0,1 42 | 1234567890,10,0,1 43 | 1234567890,10,0,1 44 | 1234567890,10,0,1 45 | 1234567890,10,0,1 46 | 1234567890,10,0,1 47 | 1234567890,10,0,1 48 | 1234567890,10,0,1 49 | 1234567890,10,0,1 50 | 1234567890,10,0,1 51 | 1234567890,10,0,1 52 | 1234567890,10,0,1 53 | 1234567890,10,0,1 54 | 1234567890,10,0,1 55 | 1234567890,10,0,1 56 | 1234567890,10,0,1 57 | 1234567890,10,0,1 58 | 1234567890,10,0,1 59 | 1234567890,10,0,1 60 | 1234567890,10,0,1 61 | 1234567890,10,0,1 62 | 1234567890,10,0,1 63 | 1234567890,10,0,1 64 | 1234567890,10,0,1 65 | 1234567890,10,0,1 66 | 1234567890,10,0,1 67 | 1234567890,10,0,1 68 | 1234567890,10,0,1 69 | 1234567890,10,0,1 70 | 1234567890,10,0,1 71 | 1234567890,10,0,1 72 | 1234567890,10,0,1 73 | 1234567890,10,0,1 74 | 1234567890,10,0,1 75 | 1234567890,10,0,1 76 | 1234567890,10,0,1 77 | 1234567890,10,0,1 78 | 1234567890,10,0,1 79 | 1234567890,10,0,1 80 | 1234567890,10,0,1 81 | 1234567890,10,0,1 82 | 1234567890,10,0,1 83 | 1234567890,10,0,1 84 | 1234567890,10,0,1 85 | 1234567890,10,0,1 86 | 1234567890,10,0,1 87 | 1234567890,10,0,1 88 | 1234567890,10,0,1 89 | 1234567890,10,0,1 90 | 1234567890,10,0,1 91 | 1234567890,10,0,1 92 | 1234567890,10,0,1 93 | 1234567890,10,0,1 94 | 1234567890,10,0,1 95 | 1234567890,10,0,1 96 | 1234567890,10,0,1 97 | 1234567890,10,0,1 98 | 1234567890,10,0,1 99 | 1234567890,10,0,1 100 | 1234567890,10,0,1 101 | 1234567890,10,0,1 102 | 1234567890,10,0,1 103 | 1234567890,10,0,1 104 | 1234567890,10,0,1 105 | 1234567890,10,0,1 106 | 1234567890,10,0,1 107 | 1234567890,10,0,1 108 | 1234567890,10,0,1 109 | 1234567890,10,0,1 110 | 1234567890,10,0,1 111 | 1234567890,10,0,1 112 | 1234567890,10,0,1 113 | 1234567890,10,0,1 114 | 1234567890,10,0,1 115 | 1234567890,10,0,1 116 | 1234567890,10,0,1 117 | 1234567890,10,0,1 118 | 1234567890,10,0,1 119 | 1234567890,10,0,1 120 | 1234567890,10,0,1 121 | 1234567890,10,0,1 122 | 1234567890,10,0,1 123 | 1234567890,10,0,1 124 | 1234567890,10,0,1 125 | 1234567890,10,0,1 126 | 1234567890,10,0,1 127 | 1234567890,10,0,1 128 | 1234567890,10,0,1 129 | 1234567890,10,0,1 130 | 1234567890,10,0,1 131 | 1234567890,10,0,1 132 | 1234567890,10,0,1 133 | 1234567890,10,0,1 134 | 1234567890,10,0,1 135 | 1234567890,10,0,1 136 | 1234567890,10,0,1 137 | -------------------------------------------------------------------------------- /program/rust/test_data/ema/price11_conf1.csv: -------------------------------------------------------------------------------- 1 | price,conf,expo,nslots 2 | 12345678901,1,0,5000 3 | 12345678901,1,0,1 4 | 12345678901,1,0,1 5 | 12345678901,1,0,1 6 | 12345678901,1,0,1 7 | 12345678901,1,0,1 8 | 12345678901,1,0,1 9 | 12345678901,1,0,1 10 | 12345678901,1,0,1 11 | 12345678901,1,0,1 12 | 12345678901,1,0,1 13 | 12345678901,1,0,1 14 | 12345678901,1,0,1 15 | 12345678901,1,0,1 16 | 12345678901,1,0,1 17 | 12345678901,1,0,1 18 | 12345678901,1,0,1 19 | 12345678901,1,0,1 20 | 12345678901,1,0,1 21 | 12345678901,1,0,1 22 | 12345678901,1,0,1 23 | 12345678901,1,0,1 24 | 12345678901,1,0,1 25 | 12345678901,1,0,1 26 | 12345678901,1,0,1 27 | 12345678901,1,0,1 28 | 12345678901,1,0,1 29 | 12345678901,1,0,1 30 | 12345678901,1,0,1 31 | 12345678901,1,0,1 32 | 12345678901,1,0,1 33 | 12345678901,1,0,1 34 | 12345678901,1,0,1 35 | 12345678901,1,0,1 36 | 12345678901,1,0,1 37 | 12345678901,1,0,1 38 | 12345678901,1,0,1 39 | 12345678901,1,0,1 40 | 12345678901,1,0,1 41 | 12345678901,1,0,1 42 | 12345678901,1,0,1 43 | 12345678901,1,0,1 44 | 12345678901,1,0,1 45 | 12345678901,1,0,1 46 | 12345678901,1,0,1 47 | 12345678901,1,0,1 48 | 12345678901,1,0,1 49 | 12345678901,1,0,1 50 | 12345678901,1,0,1 51 | 12345678901,1,0,1 52 | 12345678901,1,0,1 53 | 12345678901,1,0,1 54 | 12345678901,1,0,1 55 | 12345678901,1,0,1 56 | 12345678901,1,0,1 57 | 12345678901,1,0,1 58 | 12345678901,1,0,1 59 | 12345678901,1,0,1 60 | 12345678901,1,0,1 61 | 12345678901,1,0,1 62 | 12345678901,1,0,1 63 | 12345678901,1,0,1 64 | 12345678901,1,0,1 65 | 12345678901,1,0,1 66 | 12345678901,1,0,1 67 | 12345678901,1,0,1 68 | 12345678901,1,0,1 69 | 12345678901,1,0,1 70 | 12345678901,1,0,1 71 | 12345678901,1,0,1 72 | 12345678901,1,0,1 73 | 12345678901,1,0,1 74 | 12345678901,1,0,1 75 | 12345678901,1,0,1 76 | 12345678901,1,0,1 77 | 12345678901,1,0,1 78 | 12345678901,1,0,1 79 | 12345678901,1,0,1 80 | 12345678901,1,0,1 81 | 12345678901,1,0,1 82 | 12345678901,1,0,1 83 | 12345678901,1,0,1 84 | 12345678901,1,0,1 85 | 12345678901,1,0,1 86 | 12345678901,1,0,1 87 | 12345678901,1,0,1 88 | 12345678901,1,0,1 89 | 12345678901,1,0,1 90 | 12345678901,1,0,1 91 | 12345678901,1,0,1 92 | 12345678901,1,0,1 93 | 12345678901,1,0,1 94 | 12345678901,1,0,1 95 | 12345678901,1,0,1 96 | 12345678901,1,0,1 97 | 12345678901,1,0,1 98 | 12345678901,1,0,1 99 | 12345678901,1,0,1 100 | 12345678901,1,0,1 101 | 12345678901,1,0,1 102 | 12345678901,1,0,1 103 | 12345678901,1,0,1 104 | 12345678901,1,0,1 105 | 12345678901,1,0,1 106 | 12345678901,1,0,1 107 | 12345678901,1,0,1 108 | 12345678901,1,0,1 109 | 12345678901,1,0,1 110 | 12345678901,1,0,1 111 | 12345678901,1,0,1 112 | 12345678901,1,0,1 113 | 12345678901,1,0,1 114 | 12345678901,1,0,1 115 | 12345678901,1,0,1 116 | 12345678901,1,0,1 117 | 12345678901,1,0,1 118 | 12345678901,1,0,1 119 | 12345678901,1,0,1 120 | 12345678901,1,0,1 121 | 12345678901,1,0,1 122 | 12345678901,1,0,1 123 | 12345678901,1,0,1 124 | 12345678901,1,0,1 125 | 12345678901,1,0,1 126 | 12345678901,1,0,1 127 | 12345678901,1,0,1 128 | 12345678901,1,0,1 129 | 12345678901,1,0,1 130 | 12345678901,1,0,1 131 | 12345678901,1,0,1 132 | 12345678901,1,0,1 133 | 12345678901,1,0,1 134 | 12345678901,1,0,1 135 | 12345678901,1,0,1 136 | 12345678901,1,0,1 137 | -------------------------------------------------------------------------------- /program/rust/src/processor/init_price.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::{ 3 | accounts::{ 4 | PriceAccount, 5 | PriceEma, 6 | PriceInfo, 7 | }, 8 | deserialize::{ 9 | load, 10 | load_checked, 11 | }, 12 | instruction::InitPriceArgs, 13 | utils::{ 14 | check_exponent_range, 15 | check_permissioned_funding_account, 16 | check_valid_funding_account, 17 | pyth_assert, 18 | }, 19 | OracleError, 20 | }, 21 | bytemuck::bytes_of_mut, 22 | solana_program::{ 23 | account_info::AccountInfo, 24 | entrypoint::ProgramResult, 25 | program_error::ProgramError, 26 | program_memory::sol_memset, 27 | pubkey::Pubkey, 28 | }, 29 | std::mem::size_of, 30 | }; 31 | 32 | /// (Re)initialize price account 33 | // account[0] funding account [signer writable] 34 | // account[1] new price account [signer writable] 35 | // account[2] permissions account [] 36 | pub fn init_price( 37 | program_id: &Pubkey, 38 | accounts: &[AccountInfo], 39 | instruction_data: &[u8], 40 | ) -> ProgramResult { 41 | let cmd_args = load::(instruction_data)?; 42 | 43 | check_exponent_range(cmd_args.exponent)?; 44 | 45 | let (funding_account, price_account, permissions_account) = match accounts { 46 | [x, y, p] => Ok((x, y, p)), 47 | _ => Err(OracleError::InvalidNumberOfAccounts), 48 | }?; 49 | 50 | check_valid_funding_account(funding_account)?; 51 | check_permissioned_funding_account( 52 | program_id, 53 | price_account, 54 | funding_account, 55 | permissions_account, 56 | &cmd_args.header, 57 | )?; 58 | 59 | 60 | let mut price_data = load_checked::(price_account, cmd_args.header.version)?; 61 | pyth_assert( 62 | price_data.price_type == cmd_args.price_type, 63 | ProgramError::InvalidArgument, 64 | )?; 65 | 66 | price_data.exponent = cmd_args.exponent; 67 | 68 | price_data.last_slot_ = 0; 69 | price_data.valid_slot_ = 0; 70 | price_data.agg_.pub_slot_ = 0; 71 | price_data.prev_slot_ = 0; 72 | price_data.prev_price_ = 0; 73 | price_data.prev_conf_ = 0; 74 | price_data.prev_timestamp_ = 0; 75 | sol_memset( 76 | bytes_of_mut(&mut price_data.twap_), 77 | 0, 78 | size_of::(), 79 | ); 80 | sol_memset( 81 | bytes_of_mut(&mut price_data.twac_), 82 | 0, 83 | size_of::(), 84 | ); 85 | sol_memset( 86 | bytes_of_mut(&mut price_data.agg_), 87 | 0, 88 | size_of::(), 89 | ); 90 | for i in 0..price_data.comp_.len() { 91 | sol_memset( 92 | bytes_of_mut(&mut price_data.comp_[i].agg_), 93 | 0, 94 | size_of::(), 95 | ); 96 | sol_memset( 97 | bytes_of_mut(&mut price_data.comp_[i].latest_), 98 | 0, 99 | size_of::(), 100 | ); 101 | } 102 | 103 | Ok(()) 104 | } 105 | -------------------------------------------------------------------------------- /program/rust/src/processor/del_price.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::{ 3 | accounts::{ 4 | PriceAccount, 5 | ProductAccount, 6 | }, 7 | deserialize::{ 8 | load, 9 | load_checked, 10 | }, 11 | instruction::CommandHeader, 12 | utils::{ 13 | check_permissioned_funding_account, 14 | check_valid_funding_account, 15 | pyth_assert, 16 | }, 17 | OracleError, 18 | }, 19 | solana_program::{ 20 | account_info::AccountInfo, 21 | entrypoint::ProgramResult, 22 | program_error::ProgramError, 23 | pubkey::Pubkey, 24 | }, 25 | }; 26 | 27 | /// Delete a price account. This function will remove the link between the price account and its 28 | /// corresponding product account, then transfer any SOL in the price account to the funding 29 | /// account. This function can only delete the first price account in the linked list of 30 | // account[0] funding account [signer writable] 31 | // account[1] product account [signer writable] 32 | // account[2] price account [signer writable] 33 | // account[3] permissions account [] 34 | /// Warning: This function is dangerous and will break any programs that depend on the deleted 35 | /// price account! 36 | pub fn del_price( 37 | program_id: &Pubkey, 38 | accounts: &[AccountInfo], 39 | instruction_data: &[u8], 40 | ) -> ProgramResult { 41 | let (funding_account, product_account, price_account, permissions_account) = match accounts { 42 | [w, x, y, p] => Ok((w, x, y, p)), 43 | _ => Err(OracleError::InvalidNumberOfAccounts), 44 | }?; 45 | 46 | let cmd_args = load::(instruction_data)?; 47 | 48 | check_valid_funding_account(funding_account)?; 49 | check_permissioned_funding_account( 50 | program_id, 51 | product_account, 52 | funding_account, 53 | permissions_account, 54 | cmd_args, 55 | )?; 56 | check_permissioned_funding_account( 57 | program_id, 58 | price_account, 59 | funding_account, 60 | permissions_account, 61 | cmd_args, 62 | )?; 63 | 64 | { 65 | let mut product_data = load_checked::(product_account, cmd_args.version)?; 66 | let price_data = load_checked::(price_account, cmd_args.version)?; 67 | pyth_assert( 68 | product_data.first_price_account == *price_account.key, 69 | ProgramError::InvalidArgument, 70 | )?; 71 | 72 | pyth_assert( 73 | price_data.product_account == *product_account.key, 74 | ProgramError::InvalidArgument, 75 | )?; 76 | 77 | product_data.first_price_account = price_data.next_price_account; 78 | } 79 | 80 | // Zero out the balance of the price account to delete it. 81 | // Note that you can't use the system program's transfer instruction to do this operation, as 82 | // that instruction fails if the source account has any data. 83 | let lamports = price_account.lamports(); 84 | **price_account.lamports.borrow_mut() = 0; 85 | **funding_account.lamports.borrow_mut() += lamports; 86 | 87 | Ok(()) 88 | } 89 | -------------------------------------------------------------------------------- /program/rust/src/tests/test_full_publisher_set.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::{ 3 | accounts::PriceAccount, 4 | c_oracle_header::{ 5 | PC_NUM_COMP, 6 | PC_STATUS_TRADING, 7 | }, 8 | tests::pyth_simulator::{ 9 | PythSimulator, 10 | Quote, 11 | }, 12 | }, 13 | solana_sdk::{ 14 | signature::Keypair, 15 | signer::Signer, 16 | }, 17 | }; 18 | 19 | // Verify that the whole publisher set participates in aggregate 20 | // calculation. This is important for verifying that extra 21 | // publisher slots on Pythnet are working. Here's how this works: 22 | // 23 | // * Fill all publisher slots on a price 24 | // * Divide the price component array into two even halves: first_half, second_half 25 | // * Publish two distinct price values to either half 26 | // * Verify that the aggregate averages out to an expected value in the middle 27 | #[tokio::test] 28 | async fn test_full_publisher_set() -> Result<(), Box> { 29 | let mut sim = PythSimulator::new().await; 30 | let pub_keypairs: Vec<_> = (0..PC_NUM_COMP).map(|_idx| Keypair::new()).collect(); 31 | let pub_pubkeys: Vec<_> = pub_keypairs.iter().map(|kp| kp.pubkey()).collect(); 32 | 33 | let security_authority = Keypair::new(); 34 | let price_accounts = sim 35 | .setup_product_fixture(pub_pubkeys.as_slice(), security_authority.pubkey()) 36 | .await; 37 | let price = price_accounts["LTC"]; 38 | 39 | 40 | let n_pubs = pub_keypairs.len(); 41 | 42 | // Divide publishers into two even parts (assuming the max PC_NUM_COMP size is even) 43 | let (first_half, second_half) = pub_keypairs.split_at(n_pubs / 2); 44 | 45 | // Starting with the first publisher in each half, publish an update 46 | for (first_kp, second_kp) in first_half.iter().zip(second_half.iter()) { 47 | let first_quote = Quote { 48 | price: 100, 49 | confidence: 30, 50 | status: PC_STATUS_TRADING, 51 | }; 52 | 53 | sim.upd_price(first_kp, price, first_quote).await?; 54 | 55 | let second_quote = Quote { 56 | price: 120, 57 | confidence: 30, 58 | status: PC_STATUS_TRADING, 59 | }; 60 | 61 | sim.upd_price(second_kp, price, second_quote).await?; 62 | } 63 | 64 | // Advance slot once from 1 to 2 65 | sim.warp_to_slot(2).await?; 66 | 67 | // Final price update to trigger aggregation 68 | let first_kp = pub_keypairs.first().unwrap(); 69 | let first_quote = Quote { 70 | price: 100, 71 | confidence: 30, 72 | status: PC_STATUS_TRADING, 73 | }; 74 | sim.upd_price(first_kp, price, first_quote).await?; 75 | 76 | { 77 | let price_data = sim 78 | .get_account_data_as::(price) 79 | .await 80 | .unwrap(); 81 | 82 | if cfg!(feature = "no-default-accumulator-v2") { 83 | assert_eq!(price_data.agg_.price_, 110); 84 | assert_eq!(price_data.agg_.conf_, 20); 85 | } else { 86 | assert_eq!(price_data.agg_.price_, 0); 87 | assert_eq!(price_data.agg_.conf_, 0); 88 | } 89 | } 90 | 91 | Ok(()) 92 | } 93 | -------------------------------------------------------------------------------- /program/rust/src/processor/add_price.rs: -------------------------------------------------------------------------------- 1 | use { 2 | super::reserve_new_price_feed_index, 3 | crate::{ 4 | accounts::{ 5 | PriceAccount, 6 | PriceAccountFlags, 7 | ProductAccount, 8 | PythAccount, 9 | }, 10 | c_oracle_header::{ 11 | PC_PTYPE_UNKNOWN, 12 | PRICE_ACCOUNT_DEFAULT_MIN_PUB, 13 | }, 14 | deserialize::{ 15 | load, 16 | load_checked, 17 | }, 18 | instruction::AddPriceArgs, 19 | utils::{ 20 | check_exponent_range, 21 | check_permissioned_funding_account, 22 | check_valid_funding_account, 23 | check_valid_writable_account, 24 | pyth_assert, 25 | }, 26 | OracleError, 27 | }, 28 | solana_program::{ 29 | account_info::AccountInfo, 30 | entrypoint::ProgramResult, 31 | program_error::ProgramError, 32 | pubkey::Pubkey, 33 | }, 34 | }; 35 | 36 | /// Add new price account to a product account 37 | // account[0] funding account [signer writable] 38 | // account[1] product account [writable] 39 | // account[2] new price account [writable] 40 | // account[3] permissions account [writable] 41 | pub fn add_price( 42 | program_id: &Pubkey, 43 | accounts: &[AccountInfo], 44 | instruction_data: &[u8], 45 | ) -> ProgramResult { 46 | let cmd_args = load::(instruction_data)?; 47 | 48 | check_exponent_range(cmd_args.exponent)?; 49 | pyth_assert( 50 | cmd_args.price_type != PC_PTYPE_UNKNOWN, 51 | ProgramError::InvalidArgument, 52 | )?; 53 | 54 | 55 | let (funding_account, product_account, price_account, permissions_account) = match accounts { 56 | [x, y, z, p] => Ok((x, y, z, p)), 57 | _ => Err(OracleError::InvalidNumberOfAccounts), 58 | }?; 59 | 60 | check_valid_funding_account(funding_account)?; 61 | check_permissioned_funding_account( 62 | program_id, 63 | product_account, 64 | funding_account, 65 | permissions_account, 66 | &cmd_args.header, 67 | )?; 68 | check_permissioned_funding_account( 69 | program_id, 70 | price_account, 71 | funding_account, 72 | permissions_account, 73 | &cmd_args.header, 74 | )?; 75 | check_valid_writable_account(program_id, permissions_account)?; 76 | 77 | let mut product_data = 78 | load_checked::(product_account, cmd_args.header.version)?; 79 | 80 | let mut price_data = PriceAccount::initialize(price_account, cmd_args.header.version)?; 81 | price_data.exponent = cmd_args.exponent; 82 | price_data.price_type = cmd_args.price_type; 83 | price_data.product_account = *product_account.key; 84 | price_data.next_price_account = product_data.first_price_account; 85 | price_data.min_pub_ = PRICE_ACCOUNT_DEFAULT_MIN_PUB; 86 | price_data.feed_index = reserve_new_price_feed_index(permissions_account)?; 87 | 88 | if !cfg!(feature = "no-default-accumulator-v2") { 89 | price_data 90 | .flags 91 | .insert(PriceAccountFlags::ACCUMULATOR_V2 | PriceAccountFlags::MESSAGE_BUFFER_CLEARED); 92 | } 93 | 94 | product_data.first_price_account = *price_account.key; 95 | 96 | Ok(()) 97 | } 98 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [2.9.1] - 2021-11-03 9 | - [pyth] do not update twap with last price if agg status is unknown 10 | 11 | ## [2.9] - 2021-11-02 12 | - [pyth] add minimum number of publishers for price to be valid (not unknown) 13 | - [pyth] various minor code cleanup 14 | - [pyth] testing improvements 15 | 16 | ## [2.8.1] - 2021-10-05 17 | - [pyth] temporary code to reset twap calc on negative values 18 | 19 | ## [2.8] - 2021-10-05 20 | - [pyth] use some bits for storing exponent to avoid overflow (twap) 21 | 22 | ## [2.7] - 2021-10-02 23 | - [pyth] proper lprice/uprice sorting in confidence calculation 24 | 25 | ## [2.6] - 2021-09-28 26 | - [pyth] filter out quoters far from median 27 | - [pyth] add linker flags to detect missing defs 28 | 29 | ## [2.5] - 2021-09-22 30 | - [pyth] fixed signed division in conf interval calculation 31 | - [pyth] enabled stricter compiler flags and fixed associated warnings 32 | 33 | ## [2.4] - 2021-09-21 34 | - [config] Replaced `prodbeta` with current `mainnet` keys in the `init_key_store.sh` 35 | - Break out admin-only request/rpc classes into separate files. 36 | - [pyth_admin] Separate out admin commands into new binary. 37 | - [pyth] Consolidate CLI argument parsing. 38 | - [pyth] Remove get_pub_key command and update docs/scripts - these should by run using the solana CLI tool. 39 | - [pyth] Remove transfer and get_balance command line commands - these should by run using the solana CLI tool. 40 | - [pyth] add Host header to RPC requests 41 | - [pyth] add account filtering to get_program_accounts 42 | - [pyth] add option to disable ws connection to RPC node 43 | - [docker] add gcc-multilib package 44 | - [pyth] add python tests 45 | - [pyth] filter out zero prices in aggregation 46 | 47 | ## [2.3.1] - 2021-08-20 48 | - Fixed uninitialized variable in `manager.cpp` 49 | 50 | ## [2.3] - 2021-08-18 51 | ### Security update 52 | - [pythd] add whitelist for dashboard static files to http server to prevent directory traversal 53 | 54 | ### Others 55 | - Fix constructor parameter order and unnecessary declaration. 56 | - Add ability to send price updates without pyth_tx. 57 | - pyth.cpp: add upd_price_val for explicit price value updates (#32) 58 | - [pythd] Update user::parse_get_product code. 59 | - [pythd] Add get_product and get_all_products WS methods. 60 | - fix uninit variable 61 | 62 | ## [2.2] - 2021-08-10 63 | - [pyth] Add get_all_products command. 64 | - [pyth] Print error and return 1 on mapping key failure. 65 | - [manager] Set error state if program key is not found. 66 | - [pyth] Add hidden -d debug logging flag to help text. 67 | - [replace] slot_subscribe with get_slot 68 | - [build] Add pyth_tx to the release target 69 | - [docker] no access to secrets on pull requests 70 | - [docker] grant sudo access to pyth user 71 | 72 | ## [2.1] - 2021-07-27 73 | - [build] remove github packages 74 | - [docker] docker action to create containers 75 | - [oracle] weight twap contributions inversely with confidence interval; added testnet keys 76 | - [config] add testnet keys 77 | 78 | ## [2.0] - 2021-07-14 79 | Initial public release 80 | - fix to proxy Solana node reconnect logic in reinitializing slot leaders (origin/v2) 81 | - show tx errors in get_block 82 | -------------------------------------------------------------------------------- /program/rust/src/tests/test_del_product.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::{ 3 | accounts::MappingAccount, 4 | deserialize::load, 5 | tests::pyth_simulator::PythSimulator, 6 | }, 7 | solana_program::pubkey::Pubkey, 8 | solana_sdk::signer::Signer, 9 | std::mem::{ 10 | size_of, 11 | size_of_val, 12 | }, 13 | }; 14 | 15 | 16 | #[tokio::test] 17 | async fn test_del_product() { 18 | let mut sim = PythSimulator::new().await; 19 | let mapping_keypair = sim.init_mapping().await.unwrap(); 20 | let product1 = sim.add_product(&mapping_keypair).await.unwrap(); 21 | let product2 = sim.add_product(&mapping_keypair).await.unwrap(); 22 | let product3 = sim.add_product(&mapping_keypair).await.unwrap(); 23 | let product4 = sim.add_product(&mapping_keypair).await.unwrap(); 24 | let product5 = sim.add_product(&mapping_keypair).await.unwrap(); 25 | let _price3 = sim.add_price(&product3, -8).await.unwrap(); 26 | 27 | let mapping_keypair2 = sim.init_mapping().await.unwrap(); 28 | let product1_2 = sim.add_product(&mapping_keypair2).await.unwrap(); 29 | 30 | assert!(sim.get_account(product2.pubkey()).await.is_some()); 31 | assert!(sim.get_account(product4.pubkey()).await.is_some()); 32 | 33 | // Can't delete a product with a price account 34 | assert!(sim.del_product(&mapping_keypair, &product3).await.is_err()); 35 | // Can't delete mismatched product/mapping accounts 36 | assert!(sim 37 | .del_product(&mapping_keypair, &product1_2) 38 | .await 39 | .is_err()); 40 | 41 | assert!(sim.del_product(&mapping_keypair, &product2).await.is_ok()); 42 | 43 | assert!(sim.get_account(product2.pubkey()).await.is_none()); 44 | 45 | { 46 | let mapping_account = sim.get_account(mapping_keypair.pubkey()).await.unwrap(); 47 | let mapping_data = load::(&mapping_account.data).unwrap(); 48 | assert!(mapping_product_list_equals( 49 | mapping_data, 50 | vec![ 51 | product1.pubkey(), 52 | product5.pubkey(), 53 | product3.pubkey(), 54 | product4.pubkey() 55 | ] 56 | )); 57 | } 58 | assert!(sim.get_account(product5.pubkey()).await.is_some()); 59 | 60 | 61 | assert!(sim.del_product(&mapping_keypair, &product4).await.is_ok()); 62 | { 63 | let mapping_account = sim.get_account(mapping_keypair.pubkey()).await.unwrap(); 64 | let mapping_data = load::(&mapping_account.data).unwrap(); 65 | assert!(mapping_product_list_equals( 66 | mapping_data, 67 | vec![product1.pubkey(), product5.pubkey(), product3.pubkey()] 68 | )); 69 | } 70 | } 71 | 72 | /// Returns true if the list of products in `mapping_data` contains the keys in `expected` (in the 73 | /// same order). Also checks `mapping_data.num_` and `size_`. 74 | fn mapping_product_list_equals(mapping_data: &MappingAccount, expected: Vec) -> bool { 75 | if mapping_data.number_of_products != expected.len() as u32 { 76 | return false; 77 | } 78 | 79 | let expected_size = (size_of::() - size_of_val(&mapping_data.products_list) 80 | + expected.len() * size_of::()) as u32; 81 | if mapping_data.header.size != expected_size { 82 | return false; 83 | } 84 | &mapping_data.products_list[..expected.len()] == expected.as_slice() 85 | } 86 | -------------------------------------------------------------------------------- /program/rust/src/tests/test_aggregation.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::{ 3 | accounts::PriceAccount, 4 | processor::c_upd_aggregate, 5 | }, 6 | bytemuck::Zeroable, 7 | serde::{ 8 | Deserialize, 9 | Serialize, 10 | }, 11 | std::fs::File, 12 | }; 13 | extern crate test_generator; 14 | 15 | use test_generator::test_resources; 16 | 17 | #[test_resources("program/rust/test_data/aggregation/*.json")] 18 | fn test_quote_set(input_path_raw: &str) { 19 | // For some reason these tests have a different working directory than the macro. 20 | let input_path = input_path_raw.replace("program/rust/", ""); 21 | let result_path = input_path.replace(".json", ".result"); 22 | 23 | let file = File::open(input_path).expect("Test file not found"); 24 | let quote_set: QuoteSet = serde_json::from_reader(&file).expect("Unable to parse JSON"); 25 | 26 | let result_file = File::open(result_path).expect("Test file not found"); 27 | let expected_result: QuoteSetResult = 28 | serde_json::from_reader(&result_file).expect("Unable to parse JSON"); 29 | 30 | let current_slot = 1000; // arbitrary 31 | let current_timestamp = 1234; // also arbitrary 32 | let mut price_account: PriceAccount = PriceAccount::zeroed(); 33 | 34 | price_account.last_slot_ = current_slot; 35 | price_account.agg_.pub_slot_ = current_slot; 36 | price_account.exponent = quote_set.exponent; 37 | price_account.num_ = quote_set.quotes.len() as u32; 38 | for quote_idx in 0..quote_set.quotes.len() { 39 | let mut current_component = &mut price_account.comp_[quote_idx]; 40 | let quote = "e_set.quotes[quote_idx]; 41 | current_component.latest_.status_ = quote.status; 42 | current_component.latest_.price_ = quote.price; 43 | current_component.latest_.conf_ = quote.conf; 44 | let slot_diff = quote.slot_diff.unwrap_or(0); 45 | assert!(slot_diff > -(current_slot as i64)); 46 | current_component.latest_.pub_slot_ = ((current_slot as i64) + slot_diff) as u64; 47 | } 48 | 49 | unsafe { 50 | c_upd_aggregate( 51 | (&mut price_account as *mut PriceAccount) as *mut u8, 52 | current_slot + 1, 53 | current_timestamp, 54 | ); 55 | } 56 | 57 | // For some idiotic reason the status in the input is a number and the output is a string. 58 | let result_status: String = match price_account.agg_.status_ { 59 | 0 => "unknown", 60 | 1 => "trading", 61 | 2 => "halted", 62 | 3 => "auction", 63 | 4 => "ignored", 64 | _ => "invalid status", 65 | } 66 | .into(); 67 | 68 | let actual_result = QuoteSetResult { 69 | exponent: price_account.exponent, 70 | price: price_account.agg_.price_, 71 | conf: price_account.agg_.conf_, 72 | status: result_status, 73 | }; 74 | 75 | assert_eq!(expected_result, actual_result); 76 | } 77 | 78 | #[derive(Serialize, Deserialize, Debug)] 79 | struct Quote { 80 | price: i64, 81 | conf: u64, 82 | status: u32, 83 | slot_diff: Option, 84 | } 85 | 86 | #[derive(Serialize, Deserialize, Debug)] 87 | struct QuoteSet { 88 | exponent: i32, 89 | quotes: Vec, 90 | } 91 | 92 | #[derive(Serialize, Deserialize, Debug, PartialEq)] 93 | struct QuoteSetResult { 94 | exponent: i32, 95 | price: i64, 96 | conf: u64, 97 | status: String, 98 | } 99 | -------------------------------------------------------------------------------- /pc/key_pair.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace pc 6 | { 7 | 8 | class key_pair; 9 | 10 | class hash 11 | { 12 | public: 13 | static const size_t len = 32; 14 | hash(); 15 | hash( const hash& ); 16 | hash& operator=( const hash& ); 17 | bool operator==( const hash& )const; 18 | bool operator!=( const hash& )const; 19 | void zero(); 20 | 21 | // initialize from base58 encoded file or text 22 | bool init_from_file( const std::string& file ); 23 | bool init_from_text( const std::string& buf ); 24 | bool init_from_text( str buf ); 25 | void init_from_buf( const uint8_t * ); 26 | 27 | // encode to text buffer 28 | int enc_base58( char *buf, int buflen ) const; 29 | int enc_base58( std::string& ) const; 30 | int dec_base58( const uint8_t *buf, int buflen ); 31 | 32 | // get underlying bytes 33 | const uint8_t *data() const; 34 | 35 | protected: 36 | union{ uint64_t i_[4]; uint8_t pk_[len]; }; 37 | }; 38 | 39 | // public key 40 | class pub_key : public hash 41 | { 42 | public: 43 | pub_key(); 44 | pub_key( const pub_key& ); 45 | pub_key( const key_pair& ); 46 | pub_key& operator=( const pub_key& ); 47 | }; 48 | 49 | // private/public key pair 50 | class key_pair 51 | { 52 | public: 53 | static const size_t len = 64; 54 | 55 | void zero(); 56 | 57 | // generate new keypair 58 | void gen(); 59 | 60 | // initialize from json-format key file used by solana 61 | bool init_from_file( const std::string& file ); 62 | bool init_from_json( const char *buf, size_t len ); 63 | bool init_from_json( const std::string& buf ); 64 | 65 | // get public key part of pair 66 | void get_pub_key( pub_key& ) const; 67 | 68 | // get underlying bytes 69 | const uint8_t *data() const; 70 | 71 | private: 72 | uint8_t pk_[len]; 73 | }; 74 | 75 | // opaque ssl key handle 76 | class key_cache 77 | { 78 | public: 79 | key_cache(); 80 | ~key_cache(); 81 | void set( const key_pair& ); 82 | void *get() const; 83 | private: 84 | void *ptr_; 85 | }; 86 | 87 | // digital signature 88 | class signature 89 | { 90 | public: 91 | 92 | static const size_t len = 64; 93 | 94 | // initialize from raw bytes or bas58 encoded text 95 | void init_from_buf( const uint8_t * ); 96 | bool init_from_text( const std::string& buf ); 97 | 98 | // encode to text buffer 99 | int enc_base58( char *buf, int buflen ); 100 | int enc_base58( std::string& ); 101 | 102 | // sign message given key_pair 103 | bool sign( const uint8_t* msg, uint32_t msg_len, 104 | const key_pair& ); 105 | bool sign( const uint8_t* msg, uint32_t msg_len, 106 | const key_cache& ); 107 | 108 | // verify message given public key 109 | bool verify( const uint8_t* msg, uint32_t msg_len, 110 | const pub_key& ); 111 | 112 | // get underlying bytes 113 | const uint8_t *data() const; 114 | 115 | private: 116 | uint8_t sig_[len]; 117 | }; 118 | 119 | inline const uint8_t *hash::data() const 120 | { 121 | return pk_; 122 | } 123 | 124 | inline const uint8_t *key_pair::data() const 125 | { 126 | return pk_; 127 | } 128 | 129 | inline const uint8_t *signature::data() const 130 | { 131 | return sig_; 132 | } 133 | 134 | } 135 | --------------------------------------------------------------------------------