diff --git a/.coderabbit.yaml b/.coderabbit.yaml new file mode 100644 index 00000000..ca9c8803 --- /dev/null +++ b/.coderabbit.yaml @@ -0,0 +1,140 @@ +language: en-US +tone_instructions: '' +early_access: true +enable_free_tier: true +reviews: + # Review style + summary placement + profile: assertive + high_level_summary_placeholder: "" + review_status: false + collapse_walkthrough: true + + # Noise reduction (these default to true; we explicitly turn them off) + changed_files_summary: false + sequence_diagrams: false + estimate_code_review_effort: false + related_issues: false + related_prs: false + suggested_labels: false + labeling_instructions: [] + auto_apply_labels: false + suggested_reviewers: true + auto_assign_reviewers: false + in_progress_fortune: false + poem: false + enable_prompt_for_ai_agents: true + + # Auto-review behavior + auto_review: + enabled: true + drafts: true + auto_incremental_review: true + base_branches: [".*"] + + # Don’t auto-generate docstrings/tests (reduce chatter) + finishing_touches: + docstrings: + enabled: false + unit_tests: + enabled: true + pre_merge_checks: + docstrings: + mode: off + title: + mode: off + description: + mode: off + issue_assessment: + mode: off + + # Lightweight, high-signal tools for this repo + tools: + # Audited languages: Rust, Shell, TypeScript/JavaScript, Protobuf, Markdown, YAML, TOML, Makefile. + ast-grep: + rule_dirs: [] + util_dirs: [] + essential_rules: true + packages: [] + shellcheck: + enabled: true + markdownlint: + enabled: true + github-checks: + enabled: false + timeout_ms: 90000 + languagetool: + enabled: true + enabled_rules: [] + disabled_rules: [] + enabled_categories: [] + disabled_categories: [] + enabled_only: false + level: default + biome: + enabled: true + yamllint: + enabled: true + gitleaks: + enabled: true + eslint: + enabled: true + buf: + enabled: true + actionlint: + enabled: true + semgrep: + enabled: true + clippy: + enabled: true + oxc: + enabled: true + dotenvLint: + enabled: true + checkmake: + enabled: true + osvScanner: + enabled: true + path_instructions: + - path: "{dlp-api,src}/**" + instructions: | + Treat any usage of `.unwrap()` or `.expect()` in production Rust code as a MAJOR issue. + These should not be categorized as trivial or nit-level concerns. + Request proper error handling or explicit justification with invariants. + - path: "{tests}/**" + instructions: Usage of `.unwrap()` or `.expect()` in test code is acceptable and may be treated as trivial. +chat: + # We leave emoji/ASCII art, but no auto-replies + art: true + auto_reply: false + +knowledge_base: + # Avoid web-search commentary in reviews + web_search: + enabled: false + code_guidelines: + enabled: true + filePatterns: [] + learnings: + scope: auto + issues: + scope: auto + jira: + usage: auto + project_keys: [] + linear: + usage: auto + team_keys: [] + pull_requests: + scope: auto + mcp: + usage: auto + disabled_servers: [] +code_generation: + docstrings: + language: en-US + path_instructions: [] + unit_tests: + path_instructions: [] +issue_enrichment: + planning: + enabled: true diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 02e477a0..0edba9bf 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -51,7 +51,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Run fmt - run: cargo fmt -- --check + run: rustup component add --toolchain nightly-x86_64-unknown-linux-gnu rustfmt && cargo +nightly fmt -- --check - name: Run clippy run: cargo clippy -- --deny=warnings @@ -80,7 +80,7 @@ jobs: run: | cargo build - - name: run tests + - name: run tests (cargo test-sbf) run: | export PATH="/home/runner/.local/share/solana/install/active_release/bin:$PATH" cargo test-sbf --features unit_test_config diff --git a/Cargo.lock b/Cargo.lock index ae13c814..746957d6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,18 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "adler32" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" + [[package]] name = "aead" version = "0.5.2" @@ -62,7 +74,7 @@ dependencies = [ "solana-hash 2.2.1", "solana-message", "solana-packet", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-sdk-ids", "solana-short-vec", "solana-signature", @@ -117,6 +129,12 @@ dependencies = [ "alloc-no-stdlib", ] +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "android-tzdata" version = "0.1.1" @@ -412,7 +430,7 @@ dependencies = [ "cc", "cfg-if", "libc", - "miniz_oxide", + "miniz_oxide 0.7.4", "object", "rustc-demangle", ] @@ -618,9 +636,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.16.0" +version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" [[package]] name = "bv" @@ -719,10 +737,11 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.6" +version = "1.2.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d6dbb628b8f8555f86d0323c2eb39e3ec81901f4b83e091db8a6a76d316a333" +checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" dependencies = [ + "find-msvc-tools", "jobserver", "libc", "shlex", @@ -888,6 +907,15 @@ version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" +[[package]] +name = "core2" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b49ba7ef1ad6107f8824dbe97de947cbaac53c44e7f9756a1fba0d37c1eec505" +dependencies = [ + "memchr", +] + [[package]] name = "cpufeatures" version = "0.2.12" @@ -899,9 +927,9 @@ dependencies = [ [[package]] name = "crc32fast" -version = "1.4.2" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" dependencies = [ "cfg-if", ] @@ -967,6 +995,28 @@ dependencies = [ "subtle", ] +[[package]] +name = "ct-codecs" +version = "1.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b10589d1a5e400d61f9f38f12f884cfd080ff345de8f17efda36fe0e4a02aa8" + +[[package]] +name = "ctor" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "424e0138278faeb2b401f174ad17e715c829512d74f3d1e81eb43365c2e0590e" +dependencies = [ + "ctor-proc-macro", + "dtor", +] + +[[package]] +name = "ctor-proc-macro" +version = "0.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52560adf09603e58c9a7ee1fe1dcb95a16927b17c127f0ac02d6e768a0e25bc1" + [[package]] name = "ctr" version = "0.9.2" @@ -1053,6 +1103,12 @@ dependencies = [ "syn 2.0.93", ] +[[package]] +name = "dary_heap" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06d2e3287df1c007e74221c49ca10a95d557349e54b3a75dc2fb14712c751f04" + [[package]] name = "dashmap" version = "5.5.3" @@ -1188,6 +1244,21 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" +[[package]] +name = "dtor" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "404d02eeb088a82cfd873006cb713fe411306c7d182c344905e101fb1167d301" +dependencies = [ + "dtor-proc-macro", +] + +[[package]] +name = "dtor-proc-macro" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f678cf4a922c215c63e0de95eb1ff08a958a81d47e485cf9da1e27bf6305cfa5" + [[package]] name = "eager" version = "0.1.0" @@ -1381,6 +1452,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + [[package]] name = "five8" version = "1.0.0" @@ -1416,12 +1493,13 @@ checksum = "94474d15a76982be62ca8a39570dccce148d98c238ebb7408b0a21b2c4bdddc4" [[package]] name = "flate2" -version = "1.0.31" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f211bbe8e69bbd0cfdea405084f128ae8b4aaa6b0b522fc8f2b009084797920" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" dependencies = [ "crc32fast", - "miniz_oxide", + "miniz_oxide 0.8.9", + "zlib-rs", ] [[package]] @@ -1439,6 +1517,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + [[package]] name = "foreign-types" version = "0.3.2" @@ -1665,7 +1749,7 @@ dependencies = [ "futures-core", "futures-sink", "futures-util", - "http", + "http 0.2.12", "indexmap", "slab", "tokio", @@ -1712,6 +1796,17 @@ version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + [[package]] name = "heck" version = "0.4.1" @@ -1786,6 +1881,16 @@ dependencies = [ "itoa", ] +[[package]] +name = "http" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +dependencies = [ + "bytes", + "itoa", +] + [[package]] name = "http-body" version = "0.4.6" @@ -1793,7 +1898,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" dependencies = [ "bytes", - "http", + "http 0.2.12", "pin-project-lite", ] @@ -1826,7 +1931,7 @@ dependencies = [ "futures-core", "futures-util", "h2", - "http", + "http 0.2.12", "http-body", "httparse", "httpdate", @@ -1846,7 +1951,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" dependencies = [ "futures-util", - "http", + "http 0.2.12", "hyper", "rustls 0.21.12", "tokio", @@ -2205,6 +2310,30 @@ version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" +[[package]] +name = "libflate" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3248b8d211bd23a104a42d81b4fa8bb8ac4a3b75e7a43d85d2c9ccb6179cd74" +dependencies = [ + "adler32", + "core2", + "crc32fast", + "dary_heap", + "libflate_lz77", +] + +[[package]] +name = "libflate_lz77" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a599cb10a9cd92b1300debcef28da8f70b935ec937f44fcd1b70a7c986a11c5c" +dependencies = [ + "core2", + "hashbrown 0.16.1", + "rle-decode-fast", +] + [[package]] name = "libredox" version = "0.1.3" @@ -2264,6 +2393,38 @@ dependencies = [ "libsecp256k1-core", ] +[[package]] +name = "libsodium-rs" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7a6a6c03c914ffa9e53f76300ab4e81a86230f8b884c7f224eb6617ab685878" +dependencies = [ + "ct-codecs", + "ctor", + "libc", + "libsodium-sys-stable", + "pkg-config", + "thiserror 1.0.69", + "zeroize", +] + +[[package]] +name = "libsodium-sys-stable" +version = "1.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e5d23f4a051a13cf1085b2c5a050d4d890d80c754534cc4247eff525fa5283d" +dependencies = [ + "cc", + "libc", + "libflate", + "minisign-verify", + "pkg-config", + "tar", + "ureq", + "vcpkg", + "zip", +] + [[package]] name = "light-poseidon" version = "0.2.0" @@ -2300,9 +2461,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.25" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "lz4" @@ -2333,6 +2494,7 @@ dependencies = [ "bytemuck", "const-crypto", "magicblock-delegation-program", + "magicblock-delegation-program-api", "num_enum", "pinocchio 0.10.1", "pinocchio-associated-token-account", @@ -2343,10 +2505,12 @@ dependencies = [ "rand 0.8.5", "rkyv", "serde", - "solana-address", + "solana-address 2.0.0", "solana-curve25519", + "solana-instruction 3.1.0", "solana-program", "solana-program-test", + "solana-pubkey 3.0.0", "solana-sdk", "solana-security-txt", "solana-sha256-hasher 3.1.0", @@ -2356,6 +2520,21 @@ dependencies = [ "tokio", ] +[[package]] +name = "magicblock-delegation-program-api" +version = "0.1.0" +dependencies = [ + "bincode", + "borsh 1.5.7", + "libsodium-rs", + "magicblock-delegation-program", + "serde", + "solana-program", + "solana-sdk", + "spl-token", + "thiserror 2.0.11", +] + [[package]] name = "memchr" version = "2.7.4" @@ -2414,6 +2593,12 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +[[package]] +name = "minisign-verify" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e856fdd13623a2f5f2f54676a4ee49502a96a80ef4a62bcedd23d52427c44d43" + [[package]] name = "miniz_oxide" version = "0.7.4" @@ -2423,6 +2608,16 @@ dependencies = [ "adler", ] +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + [[package]] name = "mio" version = "1.0.1" @@ -2870,7 +3065,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfad955b2fe8f736e1ea276ebb31820d778ee69c1161146054e9b31be2e73326" dependencies = [ "solana-account-view", - "solana-address", + "solana-address 2.0.0", "solana-define-syscall 4.0.1", "solana-instruction-view", "solana-program-error 3.0.0", @@ -2883,7 +3078,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bd92823a97fb327d7509dfd7cbfa3ead56e9fc0e131972bc0e28ab7036be31a" dependencies = [ "solana-account-view", - "solana-address", + "solana-address 2.0.0", "solana-instruction-view", "solana-program-error 3.0.0", ] @@ -2926,7 +3121,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24044a0815753862b558e179e78f03f7344cb755de48617a09d7d23b50883b6c" dependencies = [ "pinocchio 0.10.1", - "solana-address", + "solana-address 2.0.0", ] [[package]] @@ -2936,16 +3131,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "febf3bbe37f4e2723b9b41a1768c6542a1ae1b1d7bcac27f892f30cabcf70ec4" dependencies = [ "solana-account-view", - "solana-address", + "solana-address 2.0.0", "solana-instruction-view", "solana-program-error 3.0.0", ] [[package]] name = "pkg-config" -version = "0.3.30" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "polyval" @@ -3350,7 +3545,7 @@ dependencies = [ "futures-core", "futures-util", "h2", - "http", + "http 0.2.12", "http-body", "hyper", "hyper-rustls", @@ -3389,7 +3584,7 @@ checksum = "5a735987236a8e238bf0296c7e351b999c188ccc11477f311b82b55c93984216" dependencies = [ "anyhow", "async-trait", - "http", + "http 0.2.12", "reqwest", "serde", "task-local-extensions", @@ -3440,6 +3635,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "rle-decode-fast" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3582f63211428f83597b51b2ddb88e2a91a9d52d12831f9d08f5e624e8977422" + [[package]] name = "rustc-demangle" version = "0.1.24" @@ -3690,9 +3891,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.226" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dca6411025b24b60bfa7ec1fe1f8e710ac09782dca409ee8237ba74b51295fd" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ "serde_core", "serde_derive", @@ -3718,18 +3919,18 @@ dependencies = [ [[package]] name = "serde_core" -version = "1.0.226" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba2ba63999edb9dac981fb34b3e5c0d111a69b0924e253ed29d83f7c99e966a4" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.226" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8db53ae22f34573731bafa1db20f04027b2d25e02d8205921b569171699cdb33" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -3864,6 +4065,12 @@ version = "1.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" +[[package]] +name = "simd-adler32" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" + [[package]] name = "simdutf8" version = "0.1.5" @@ -3923,8 +4130,8 @@ dependencies = [ "serde_derive", "solana-account-info", "solana-clock", - "solana-instruction", - "solana-pubkey", + "solana-instruction 2.2.1", + "solana-pubkey 2.2.1", "solana-sdk-ids", "solana-sysvar", ] @@ -3941,7 +4148,7 @@ dependencies = [ "serde_derive", "serde_json", "solana-account", - "solana-pubkey", + "solana-pubkey 2.2.1", "zstd", ] @@ -3955,7 +4162,7 @@ dependencies = [ "serde", "solana-program-error 2.2.1", "solana-program-memory", - "solana-pubkey", + "solana-pubkey 2.2.1", ] [[package]] @@ -3964,7 +4171,7 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f37ca34c37f92ee341b73d5ce7c8ef5bb38e9a87955b4bd343c63fa18b149215" dependencies = [ - "solana-address", + "solana-address 2.0.0", "solana-program-error 3.0.0", ] @@ -4007,7 +4214,7 @@ dependencies = [ "solana-measure", "solana-metrics", "solana-nohash-hasher", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-rayon-threadlimit", "solana-sdk", "solana-svm-transaction", @@ -4017,6 +4224,15 @@ dependencies = [ "thiserror 2.0.11", ] +[[package]] +name = "solana-address" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2ecac8e1b7f74c2baa9e774c42817e3e75b20787134b76cc4d45e8a604488f5" +dependencies = [ + "solana-address 2.0.0", +] + [[package]] name = "solana-address" version = "2.0.0" @@ -4030,8 +4246,10 @@ dependencies = [ "five8", "five8_const 1.0.0", "serde", + "solana-atomic-u64 3.0.1", "solana-define-syscall 4.0.1", "solana-program-error 3.0.0", + "solana-sanitize 3.0.1", "solana-sha256-hasher 3.1.0", ] @@ -4046,8 +4264,8 @@ dependencies = [ "serde", "serde_derive", "solana-clock", - "solana-instruction", - "solana-pubkey", + "solana-instruction 2.2.1", + "solana-pubkey 2.2.1", "solana-sdk-ids", "solana-slot-hashes", ] @@ -4067,11 +4285,11 @@ dependencies = [ "solana-bincode", "solana-clock", "solana-feature-set", - "solana-instruction", + "solana-instruction 2.2.1", "solana-log-collector", "solana-packet", "solana-program-runtime", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-system-interface", "solana-transaction-context", "thiserror 2.0.11", @@ -4086,6 +4304,15 @@ dependencies = [ "parking_lot", ] +[[package]] +name = "solana-atomic-u64" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "085db4906d89324cef2a30840d59eaecf3d4231c560ec7c9f6614a93c652f501" +dependencies = [ + "parking_lot", +] + [[package]] name = "solana-banks-client" version = "2.2.0" @@ -4156,7 +4383,7 @@ checksum = "19a3787b8cf9c9fe3dd360800e8b70982b9e5a8af9e11c354b6665dd4a003adc" dependencies = [ "bincode", "serde", - "solana-instruction", + "solana-instruction 2.2.1", ] [[package]] @@ -4168,7 +4395,7 @@ dependencies = [ "blake3", "solana-define-syscall 2.2.1", "solana-hash 2.2.1", - "solana-sanitize", + "solana-sanitize 2.2.1", ] [[package]] @@ -4218,7 +4445,7 @@ dependencies = [ "solana-curve25519", "solana-feature-set", "solana-hash 2.2.1", - "solana-instruction", + "solana-instruction 2.2.1", "solana-keccak-hasher", "solana-loader-v3-interface", "solana-loader-v4-interface", @@ -4230,7 +4457,7 @@ dependencies = [ "solana-program-entrypoint", "solana-program-memory", "solana-program-runtime", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-sbpf", "solana-sdk-ids", "solana-secp256k1-recover", @@ -4261,7 +4488,7 @@ dependencies = [ "rand 0.8.5", "solana-clock", "solana-measure", - "solana-pubkey", + "solana-pubkey 2.2.1", "tempfile", ] @@ -4278,7 +4505,7 @@ dependencies = [ "solana-feature-set", "solana-loader-v4-program", "solana-program-runtime", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-sdk-ids", "solana-stake-program", "solana-system-program", @@ -4303,7 +4530,7 @@ dependencies = [ "solana-config-program", "solana-feature-set", "solana-loader-v4-program", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-sdk-ids", "solana-stake-program", "solana-system-program", @@ -4332,11 +4559,11 @@ dependencies = [ "solana-connection-cache", "solana-epoch-info", "solana-hash 2.2.1", - "solana-instruction", + "solana-instruction 2.2.1", "solana-keypair", "solana-measure", "solana-message", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-pubsub-client", "solana-quic-client", "solana-quic-definitions", @@ -4366,10 +4593,10 @@ dependencies = [ "solana-commitment-config", "solana-epoch-info", "solana-hash 2.2.1", - "solana-instruction", + "solana-instruction 2.2.1", "solana-keypair", "solana-message", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-signature", "solana-signer", "solana-system-interface", @@ -4433,9 +4660,9 @@ dependencies = [ "solana-compute-budget", "solana-compute-budget-interface", "solana-feature-set", - "solana-instruction", + "solana-instruction 2.2.1", "solana-packet", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-sdk-ids", "solana-svm-transaction", "solana-transaction-error", @@ -4451,7 +4678,7 @@ dependencies = [ "borsh 1.5.7", "serde", "serde_derive", - "solana-instruction", + "solana-instruction 2.2.1", "solana-sdk-ids", ] @@ -4477,11 +4704,11 @@ dependencies = [ "serde_derive", "solana-account", "solana-bincode", - "solana-instruction", + "solana-instruction 2.2.1", "solana-log-collector", "solana-packet", "solana-program-runtime", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-sdk-ids", "solana-short-vec", "solana-stake-interface", @@ -4533,7 +4760,7 @@ dependencies = [ "solana-fee-structure", "solana-metrics", "solana-packet", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-runtime-transaction", "solana-sdk-ids", "solana-svm-transaction", @@ -4550,9 +4777,9 @@ checksum = "8dc71126edddc2ba014622fc32d0f5e2e78ec6c5a1e0eb511b85618c09e9ea11" dependencies = [ "solana-account-info", "solana-define-syscall 2.2.1", - "solana-instruction", + "solana-instruction 2.2.1", "solana-program-error 2.2.1", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-stable-layout", ] @@ -4612,7 +4839,7 @@ dependencies = [ "bytemuck_derive", "ed25519-dalek", "solana-feature-set", - "solana-instruction", + "solana-instruction 2.2.1", "solana-precompile-error", "solana-sdk-ids", ] @@ -4649,7 +4876,7 @@ checksum = "96c5fd2662ae7574810904585fd443545ed2b568dbd304b25a31e79ccc76e81b" dependencies = [ "siphasher", "solana-hash 2.2.1", - "solana-pubkey", + "solana-pubkey 2.2.1", ] [[package]] @@ -4676,11 +4903,11 @@ dependencies = [ "solana-address-lookup-table-interface", "solana-clock", "solana-hash 2.2.1", - "solana-instruction", + "solana-instruction 2.2.1", "solana-keccak-hasher", "solana-message", "solana-nonce", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-sdk-ids", "solana-system-interface", "thiserror 2.0.11", @@ -4697,9 +4924,9 @@ dependencies = [ "serde_derive", "solana-account", "solana-account-info", - "solana-instruction", + "solana-instruction 2.2.1", "solana-program-error 2.2.1", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-rent", "solana-sdk-ids", "solana-system-interface", @@ -4715,7 +4942,7 @@ dependencies = [ "lazy_static", "solana-epoch-schedule", "solana-hash 2.2.1", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-sha256-hasher 2.2.1", ] @@ -4775,7 +5002,7 @@ dependencies = [ "solana-logger", "solana-native-token", "solana-poh-config", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-rent", "solana-sdk-ids", "solana-sha256-hasher 2.2.1", @@ -4807,8 +5034,8 @@ dependencies = [ "js-sys", "serde", "serde_derive", - "solana-atomic-u64", - "solana-sanitize", + "solana-atomic-u64 2.2.1", + "solana-sanitize 2.2.1", "wasm-bindgen", ] @@ -4835,7 +5062,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f623a26022a600a64fc233412808d79c5a135952eee643c127c4b42aa089b8aa" dependencies = [ "bytemuck", - "solana-pubkey", + "solana-pubkey 2.2.1", ] [[package]] @@ -4852,10 +5079,33 @@ dependencies = [ "serde", "serde_derive", "solana-define-syscall 2.2.1", - "solana-pubkey", + "solana-pubkey 2.2.1", "wasm-bindgen", ] +[[package]] +name = "solana-instruction" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee1b699a2c1518028a9982e255e0eca10c44d90006542d9d7f9f40dbce3f7c78" +dependencies = [ + "borsh 1.5.7", + "serde", + "solana-define-syscall 4.0.1", + "solana-instruction-error", + "solana-pubkey 4.0.0", +] + +[[package]] +name = "solana-instruction-error" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d3d048edaaeef5a3dc8c01853e585539a74417e4c2d43a9e2c161270045b838" +dependencies = [ + "num-traits", + "solana-program-error 3.0.0", +] + [[package]] name = "solana-instruction-view" version = "1.0.0" @@ -4863,7 +5113,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60147e4d0a4620013df40bf30a86dd299203ff12fcb8b593cd51014fce0875d8" dependencies = [ "solana-account-view", - "solana-address", + "solana-address 2.0.0", "solana-define-syscall 4.0.1", "solana-program-error 3.0.0", ] @@ -4876,10 +5126,10 @@ checksum = "427f2d0d6dc0bb49f16cef5e7f975180d2e80aab9bdd3b2af68e2d029ec63f43" dependencies = [ "bitflags 2.8.0", "solana-account-info", - "solana-instruction", + "solana-instruction 2.2.1", "solana-program-error 2.2.1", - "solana-pubkey", - "solana-sanitize", + "solana-pubkey 2.2.1", + "solana-sanitize 2.2.1", "solana-sdk-ids", "solana-serialize-utils", "solana-sysvar-id", @@ -4894,7 +5144,7 @@ dependencies = [ "sha3", "solana-define-syscall 2.2.1", "solana-hash 2.2.1", - "solana-sanitize", + "solana-sanitize 2.2.1", ] [[package]] @@ -4908,7 +5158,7 @@ dependencies = [ "ed25519-dalek-bip32", "rand 0.7.3", "solana-derivation-path", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-seed-derivable", "solana-seed-phrase", "solana-signature", @@ -4950,8 +5200,8 @@ dependencies = [ "serde", "serde_bytes", "serde_derive", - "solana-instruction", - "solana-pubkey", + "solana-instruction 2.2.1", + "solana-pubkey 2.2.1", "solana-sdk-ids", ] @@ -4964,8 +5214,8 @@ dependencies = [ "serde", "serde_bytes", "serde_derive", - "solana-instruction", - "solana-pubkey", + "solana-instruction 2.2.1", + "solana-pubkey 2.2.1", "solana-sdk-ids", "solana-system-interface", ] @@ -4979,8 +5229,8 @@ dependencies = [ "serde", "serde_bytes", "serde_derive", - "solana-instruction", - "solana-pubkey", + "solana-instruction 2.2.1", + "solana-pubkey 2.2.1", "solana-sdk-ids", "solana-system-interface", ] @@ -4997,14 +5247,14 @@ dependencies = [ "solana-bincode", "solana-bpf-loader-program", "solana-compute-budget", - "solana-instruction", + "solana-instruction 2.2.1", "solana-loader-v3-interface", "solana-loader-v4-interface", "solana-log-collector", "solana-measure", "solana-packet", "solana-program-runtime", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-sbpf", "solana-sdk-ids", "solana-transaction-context", @@ -5050,9 +5300,9 @@ dependencies = [ "serde_derive", "solana-bincode", "solana-hash 2.2.1", - "solana-instruction", - "solana-pubkey", - "solana-sanitize", + "solana-instruction 2.2.1", + "solana-pubkey 2.2.1", + "solana-sanitize 2.2.1", "solana-sdk-ids", "solana-short-vec", "solana-system-interface", @@ -5131,7 +5381,7 @@ dependencies = [ "serde_derive", "solana-fee-calculator", "solana-hash 2.2.1", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-sha256-hasher 2.2.1", ] @@ -5156,8 +5406,8 @@ dependencies = [ "num_enum", "solana-hash 2.2.1", "solana-packet", - "solana-pubkey", - "solana-sanitize", + "solana-pubkey 2.2.1", + "solana-sanitize 2.2.1", "solana-sha256-hasher 2.2.1", "solana-signature", "solana-signer", @@ -5201,7 +5451,7 @@ dependencies = [ "solana-message", "solana-metrics", "solana-packet", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-rayon-threadlimit", "solana-sdk-ids", "solana-short-vec", @@ -5252,7 +5502,7 @@ dependencies = [ "solana-feature-set", "solana-message", "solana-precompile-error", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-sdk-ids", "solana-secp256k1-program", "solana-secp256r1-program", @@ -5264,7 +5514,7 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81a57a24e6a4125fc69510b6774cd93402b943191b6cddad05de7281491c90fe" dependencies = [ - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-signature", "solana-signer", ] @@ -5296,7 +5546,7 @@ dependencies = [ "serde_derive", "solana-account-info", "solana-address-lookup-table-interface", - "solana-atomic-u64", + "solana-atomic-u64 2.2.1", "solana-big-mod-exp", "solana-bincode", "solana-blake3-hasher", @@ -5311,7 +5561,7 @@ dependencies = [ "solana-feature-gate-interface", "solana-fee-calculator", "solana-hash 2.2.1", - "solana-instruction", + "solana-instruction 2.2.1", "solana-instructions-sysvar", "solana-keccak-hasher", "solana-last-restart-slot", @@ -5327,9 +5577,9 @@ dependencies = [ "solana-program-memory", "solana-program-option", "solana-program-pack", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-rent", - "solana-sanitize", + "solana-sanitize 2.2.1", "solana-sdk-ids", "solana-sdk-macro", "solana-secp256k1-recover", @@ -5358,7 +5608,7 @@ dependencies = [ "solana-account-info", "solana-msg", "solana-program-error 2.2.1", - "solana-pubkey", + "solana-pubkey 2.2.1", ] [[package]] @@ -5372,9 +5622,9 @@ dependencies = [ "serde", "serde_derive", "solana-decode-error", - "solana-instruction", + "solana-instruction 2.2.1", "solana-msg", - "solana-pubkey", + "solana-pubkey 2.2.1", ] [[package]] @@ -5429,13 +5679,13 @@ dependencies = [ "solana-epoch-schedule", "solana-feature-set", "solana-hash 2.2.1", - "solana-instruction", + "solana-instruction 2.2.1", "solana-last-restart-slot", "solana-log-collector", "solana-measure", "solana-metrics", "solana-precompiles", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-rent", "solana-sbpf", "solana-sdk-ids", @@ -5471,7 +5721,7 @@ dependencies = [ "solana-compute-budget", "solana-feature-set", "solana-inline-spl", - "solana-instruction", + "solana-instruction 2.2.1", "solana-log-collector", "solana-logger", "solana-program-runtime", @@ -5505,14 +5755,32 @@ dependencies = [ "rand 0.8.5", "serde", "serde_derive", - "solana-atomic-u64", + "solana-atomic-u64 2.2.1", "solana-decode-error", "solana-define-syscall 2.2.1", - "solana-sanitize", + "solana-sanitize 2.2.1", "solana-sha256-hasher 2.2.1", "wasm-bindgen", ] +[[package]] +name = "solana-pubkey" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8909d399deb0851aa524420beeb5646b115fd253ef446e35fe4504c904da3941" +dependencies = [ + "solana-address 1.1.0", +] + +[[package]] +name = "solana-pubkey" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6f7104d456b58e1418c21a8581e89810278d1190f70f27ece7fc0b2c9282a57" +dependencies = [ + "solana-address 2.0.0", +] + [[package]] name = "solana-pubsub-client" version = "2.2.0" @@ -5529,7 +5797,7 @@ dependencies = [ "serde_json", "solana-account-decoder-client-types", "solana-clock", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-rpc-client-api", "solana-signature", "thiserror 2.0.11", @@ -5560,7 +5828,7 @@ dependencies = [ "solana-measure", "solana-metrics", "solana-net-utils", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-quic-definitions", "solana-rpc-client-api", "solana-signer", @@ -5615,7 +5883,7 @@ dependencies = [ "solana-clock", "solana-epoch-schedule", "solana-genesis-config", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-rent", "solana-sdk-ids", ] @@ -5626,7 +5894,7 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f6f9113c6003492e74438d1288e30cffa8ccfdc2ef7b49b9e816d8034da18cd" dependencies = [ - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-reward-info", ] @@ -5638,7 +5906,7 @@ checksum = "2b293f4246626c0e0a991531f08848a713ada965612e99dc510963f04d12cae7" dependencies = [ "lazy_static", "solana-feature-set", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-sdk-ids", ] @@ -5678,9 +5946,9 @@ dependencies = [ "solana-epoch-schedule", "solana-feature-gate-interface", "solana-hash 2.2.1", - "solana-instruction", + "solana-instruction 2.2.1", "solana-message", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-rpc-client-api", "solana-signature", "solana-transaction", @@ -5713,7 +5981,7 @@ dependencies = [ "solana-fee-calculator", "solana-inflation", "solana-inline-spl", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-signer", "solana-transaction-error", "solana-transaction-status-client-types", @@ -5732,7 +6000,7 @@ dependencies = [ "solana-hash 2.2.1", "solana-message", "solana-nonce", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-rpc-client", "solana-sdk-ids", "thiserror 2.0.11", @@ -5800,7 +6068,7 @@ dependencies = [ "solana-perf", "solana-program", "solana-program-runtime", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-rayon-threadlimit", "solana-runtime-transaction", "solana-sdk", @@ -5836,7 +6104,7 @@ dependencies = [ "solana-compute-budget-instruction", "solana-hash 2.2.1", "solana-message", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-sdk-ids", "solana-signature", "solana-svm-transaction", @@ -5851,6 +6119,12 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61f1bc1357b8188d9c4a3af3fc55276e56987265eb7ad073ae6f8180ee54cecf" +[[package]] +name = "solana-sanitize" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf09694a0fc14e5ffb18f9b7b7c0f15ecb6eac5b5610bf76a1853459d19daf9" + [[package]] name = "solana-sbpf" version = "0.10.0" @@ -5896,7 +6170,7 @@ dependencies = [ "solana-genesis-config", "solana-hard-forks", "solana-inflation", - "solana-instruction", + "solana-instruction 2.2.1", "solana-keypair", "solana-message", "solana-native-token", @@ -5909,13 +6183,13 @@ dependencies = [ "solana-presigner", "solana-program", "solana-program-memory", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-quic-definitions", "solana-rent-collector", "solana-rent-debits", "solana-reserved-account-keys", "solana-reward-info", - "solana-sanitize", + "solana-sanitize 2.2.1", "solana-sdk-ids", "solana-sdk-macro", "solana-secp256k1-program", @@ -5945,7 +6219,7 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c5d8b9cc68d5c88b062a33e23a6466722467dde0035152d8fb1afbcdf350a5f" dependencies = [ - "solana-pubkey", + "solana-pubkey 2.2.1", ] [[package]] @@ -5973,7 +6247,7 @@ dependencies = [ "serde_derive", "sha3", "solana-feature-set", - "solana-instruction", + "solana-instruction 2.2.1", "solana-precompile-error", "solana-sdk-ids", ] @@ -5999,7 +6273,7 @@ dependencies = [ "bytemuck", "openssl", "solana-feature-set", - "solana-instruction", + "solana-instruction 2.2.1", "solana-precompile-error", "solana-sdk-ids", ] @@ -6073,9 +6347,9 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "817a284b63197d2b27afdba829c5ab34231da4a9b4e763466a003c40ca4f535e" dependencies = [ - "solana-instruction", - "solana-pubkey", - "solana-sanitize", + "solana-instruction 2.2.1", + "solana-pubkey 2.2.1", + "solana-sanitize 2.2.1", ] [[package]] @@ -6132,7 +6406,7 @@ dependencies = [ "serde", "serde-big-array", "serde_derive", - "solana-sanitize", + "solana-sanitize 2.2.1", ] [[package]] @@ -6141,7 +6415,7 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c41991508a4b02f021c1342ba00bcfa098630b213726ceadc7cb032e051975b" dependencies = [ - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-signature", "solana-transaction-error", ] @@ -6178,8 +6452,8 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f14f7d02af8f2bc1b5efeeae71bc1c2b7f0f65cd75bcc7d8180f2c762a57f54" dependencies = [ - "solana-instruction", - "solana-pubkey", + "solana-instruction 2.2.1", + "solana-pubkey 2.2.1", ] [[package]] @@ -6196,9 +6470,9 @@ dependencies = [ "solana-clock", "solana-cpi", "solana-decode-error", - "solana-instruction", + "solana-instruction 2.2.1", "solana-program-error 2.2.1", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-system-interface", "solana-sysvar-id", ] @@ -6217,12 +6491,12 @@ dependencies = [ "solana-config-program", "solana-feature-set", "solana-genesis-config", - "solana-instruction", + "solana-instruction 2.2.1", "solana-log-collector", "solana-native-token", "solana-packet", "solana-program-runtime", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-rent", "solana-sdk-ids", "solana-stake-interface", @@ -6265,7 +6539,7 @@ dependencies = [ "solana-net-utils", "solana-packet", "solana-perf", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-quic-definitions", "solana-signature", "solana-signer", @@ -6299,7 +6573,7 @@ dependencies = [ "solana-feature-set", "solana-fee-structure", "solana-hash 2.2.1", - "solana-instruction", + "solana-instruction 2.2.1", "solana-instructions-sysvar", "solana-loader-v4-program", "solana-log-collector", @@ -6310,7 +6584,7 @@ dependencies = [ "solana-precompiles", "solana-program", "solana-program-runtime", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-rent", "solana-rent-debits", "solana-sdk", @@ -6341,7 +6615,7 @@ checksum = "11ffa8d1463d6813433cd4f31ec1c96abc85dc693f29b80986a77186dfe5e144" dependencies = [ "solana-hash 2.2.1", "solana-message", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-sdk-ids", "solana-signature", "solana-transaction", @@ -6358,8 +6632,8 @@ dependencies = [ "serde", "serde_derive", "solana-decode-error", - "solana-instruction", - "solana-pubkey", + "solana-instruction 2.2.1", + "solana-pubkey 2.2.1", "wasm-bindgen", ] @@ -6375,13 +6649,13 @@ dependencies = [ "serde_derive", "solana-account", "solana-bincode", - "solana-instruction", + "solana-instruction 2.2.1", "solana-log-collector", "solana-nonce", "solana-nonce-account", "solana-packet", "solana-program-runtime", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-sdk-ids", "solana-system-interface", "solana-sysvar", @@ -6398,7 +6672,7 @@ dependencies = [ "solana-hash 2.2.1", "solana-keypair", "solana-message", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-signer", "solana-system-interface", "solana-transaction", @@ -6424,15 +6698,15 @@ dependencies = [ "solana-epoch-schedule", "solana-fee-calculator", "solana-hash 2.2.1", - "solana-instruction", + "solana-instruction 2.2.1", "solana-instructions-sysvar", "solana-last-restart-slot", "solana-program-entrypoint", "solana-program-error 2.2.1", "solana-program-memory", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-rent", - "solana-sanitize", + "solana-sanitize 2.2.1", "solana-sdk-ids", "solana-sdk-macro", "solana-slot-hashes", @@ -6447,7 +6721,7 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5762b273d3325b047cfda250787f8d796d781746860d5d0a746ee29f3e8812c1" dependencies = [ - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-sdk-ids", ] @@ -6467,10 +6741,10 @@ dependencies = [ "solana-connection-cache", "solana-epoch-info", "solana-hash 2.2.1", - "solana-instruction", + "solana-instruction 2.2.1", "solana-keypair", "solana-message", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-rpc-client", "solana-rpc-client-api", "solana-signature", @@ -6494,7 +6768,7 @@ checksum = "6f59d5f154e1b2fb4aef3257af4b4823e2c2e61c3d77e52e5ef28464d37453c4" dependencies = [ "eager", "enum-iterator", - "solana-pubkey", + "solana-pubkey 2.2.1", ] [[package]] @@ -6505,7 +6779,7 @@ checksum = "6a9da625758b52894dfde5d72f3b78f5141045ec5296a5acadec934dd2ccbb1b" dependencies = [ "rustls 0.23.23", "solana-keypair", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-signer", "x509-parser", ] @@ -6531,7 +6805,7 @@ dependencies = [ "solana-measure", "solana-message", "solana-net-utils", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-pubsub-client", "solana-quic-definitions", "solana-rpc-client", @@ -6556,13 +6830,13 @@ dependencies = [ "solana-bincode", "solana-feature-set", "solana-hash 2.2.1", - "solana-instruction", + "solana-instruction 2.2.1", "solana-keypair", "solana-message", "solana-precompiles", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-reserved-account-keys", - "solana-sanitize", + "solana-sanitize 2.2.1", "solana-sdk-ids", "solana-short-vec", "solana-signature", @@ -6582,8 +6856,8 @@ dependencies = [ "serde", "serde_derive", "solana-account", - "solana-instruction", - "solana-pubkey", + "solana-instruction 2.2.1", + "solana-pubkey 2.2.1", "solana-rent", "solana-signature", ] @@ -6596,8 +6870,8 @@ checksum = "222a9dc8fdb61c6088baab34fc3a8b8473a03a7a5fd404ed8dd502fa79b67cb1" dependencies = [ "serde", "serde_derive", - "solana-instruction", - "solana-sanitize", + "solana-instruction 2.2.1", + "solana-sanitize 2.2.1", ] [[package]] @@ -6673,7 +6947,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73db72aa7dfb35bc8715cd9990a68671e4d970fff63080c45fc44fed59f54946" dependencies = [ "assert_matches", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-runtime-transaction", "solana-transaction", "static_assertions", @@ -6695,7 +6969,7 @@ dependencies = [ "serde", "serde_derive", "solana-feature-set", - "solana-sanitize", + "solana-sanitize 2.2.1", "solana-serde-varint", ] @@ -6713,9 +6987,9 @@ dependencies = [ "solana-bincode", "solana-clock", "solana-hash 2.2.1", - "solana-instruction", + "solana-instruction 2.2.1", "solana-packet", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-sdk-ids", "solana-signature", "solana-svm-transaction", @@ -6738,8 +7012,8 @@ dependencies = [ "solana-clock", "solana-decode-error", "solana-hash 2.2.1", - "solana-instruction", - "solana-pubkey", + "solana-instruction 2.2.1", + "solana-pubkey 2.2.1", "solana-rent", "solana-sdk-ids", "solana-serde-varint", @@ -6766,11 +7040,11 @@ dependencies = [ "solana-epoch-schedule", "solana-feature-set", "solana-hash 2.2.1", - "solana-instruction", + "solana-instruction 2.2.1", "solana-keypair", "solana-packet", "solana-program-runtime", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-rent", "solana-sdk-ids", "solana-signer", @@ -6790,7 +7064,7 @@ dependencies = [ "bytemuck", "num-derive", "num-traits", - "solana-instruction", + "solana-instruction 2.2.1", "solana-log-collector", "solana-program-runtime", "solana-sdk-ids", @@ -6821,8 +7095,8 @@ dependencies = [ "serde_json", "sha3", "solana-derivation-path", - "solana-instruction", - "solana-pubkey", + "solana-instruction 2.2.1", + "solana-pubkey 2.2.1", "solana-sdk-ids", "solana-seed-derivable", "solana-seed-phrase", @@ -6844,7 +7118,7 @@ dependencies = [ "num-derive", "num-traits", "solana-feature-set", - "solana-instruction", + "solana-instruction 2.2.1", "solana-log-collector", "solana-program-runtime", "solana-sdk-ids", @@ -6875,8 +7149,8 @@ dependencies = [ "sha3", "solana-curve25519", "solana-derivation-path", - "solana-instruction", - "solana-pubkey", + "solana-instruction 2.2.1", + "solana-pubkey 2.2.1", "solana-sdk-ids", "solana-seed-derivable", "solana-seed-phrase", @@ -6902,6 +7176,21 @@ dependencies = [ "lock_api", ] +[[package]] +name = "spl-token" +version = "4.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e9e171cbcb4b1f72f6d78ed1e975cb467f56825c27d09b8dd2608e4e7fc8b3b" +dependencies = [ + "arrayref", + "bytemuck", + "num-derive", + "num-traits", + "num_enum", + "solana-program", + "thiserror 1.0.69", +] + [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -7055,9 +7344,9 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tar" -version = "0.4.43" +version = "0.4.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c65998313f8e17d0d553d28f91a0df93e4dbbbf770279c7bc21ca0f09ea1a1f6" +checksum = "1d863878d212c87a19c1a610eb53bb01fe12951c0501cf5a0d65f724914a667a" dependencies = [ "filetime", "libc", @@ -7456,7 +7745,7 @@ dependencies = [ "byteorder", "bytes", "data-encoding", - "http", + "http 0.2.12", "httparse", "log", "rand 0.8.5", @@ -7468,6 +7757,12 @@ dependencies = [ "webpki-roots 0.24.0", ] +[[package]] +name = "typed-path" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e28f89b80c87b8fb0cf04ab448d5dd0dd0ade2f8891bae878de66a75a28600e" + [[package]] name = "typenum" version = "1.17.0" @@ -7529,6 +7824,31 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[package]] +name = "ureq" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc97a28575b85cfedf2a7e7d3cc64b3e11bd8ac766666318003abbacc7a21fc" +dependencies = [ + "base64 0.22.1", + "log", + "percent-encoding", + "ureq-proto", + "utf-8", +] + +[[package]] +name = "ureq-proto" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d81f9efa9df032be5934a46a068815a10a042b494b6a58cb0a1a97bb5467ed6f" +dependencies = [ + "base64 0.22.1", + "http 1.4.0", + "httparse", + "log", +] + [[package]] name = "uriparse" version = "0.6.4" @@ -8130,6 +8450,38 @@ dependencies = [ "syn 2.0.93", ] +[[package]] +name = "zip" +version = "7.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c42e33efc22a0650c311c2ef19115ce232583abbe80850bc8b66509ebef02de0" +dependencies = [ + "crc32fast", + "flate2", + "indexmap", + "memchr", + "typed-path", + "zopfli", +] + +[[package]] +name = "zlib-rs" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c745c48e1007337ed136dc99df34128b9faa6ed542d80a1c673cf55a6d7236c8" + +[[package]] +name = "zopfli" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249" +dependencies = [ + "bumpalo", + "crc32fast", + "log", + "simd-adler32", +] + [[package]] name = "zstd" version = "0.13.2" diff --git a/Cargo.toml b/Cargo.toml index ce1ed002..c1727766 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,22 +11,33 @@ repository = "https://github.com/magicblock-labs/delegation-program" readme = "./README.md" keywords = ["solana", "crypto", "delegation", "ephemeral-rollups", "magicblock"] +[workspace] +members = [".", "dlp-api"] + [lib] crate-type = ["cdylib", "lib"] name = "dlp" [features] -no-entrypoint = [] -sdk = ["no-entrypoint"] -program = [ +# Leaf features (used directly in code) +pinocchio-rt = [ "dep:pinocchio", "dep:pinocchio-log", "dep:pinocchio-pubkey", "dep:pinocchio-system", "dep:const-crypto", - "dep:rkyv", "dep:solana-curve25519", ] +diff = ["pinocchio-rt", "dep:rkyv"] +processor = ["diff"] +entrypoint = [] + +# Back-compat: keep name but make it opt-out of entrypoint. +no-entrypoint = [] + +# Use-case features (composed of leaf features) +program = ["processor", "entrypoint"] +sdk = ["diff"] # Default build = program path + security txt default = ["program", "solana-security-txt"] @@ -43,6 +54,7 @@ num_enum = "^0.7.2" rkyv = { version = "0.7.45", optional = true } solana-curve25519 = { version = ">=2.2", optional = true } solana-program = { version = ">=1.16, <3.0.0" } +solana-instruction = "3.0.0" solana-sha256-hasher = "3.1.0" solana-security-txt = { version = ">=1.1", optional = true } static_assertions = "1.1.0" @@ -60,12 +72,17 @@ pinocchio-associated-token-account = { version = "0.3.0" , optional = true } solana-address = { version = "2.0", features = ["bytemuck", "decode", "syscalls", "curve25519", "std"] } # manually resolves the conflict with a pinned version of serde -serde = "=1.0.226" +serde = { version = "1.0.228", features = ["derive"] } +solana-sdk = { version = ">=1.16", optional = true } +rand = { version = "=0.8.5", features = ["small_rng"], optional = true } + [dev-dependencies] assertables = "9.8.2" magicblock-delegation-program = { path = ".", features = ["unit_test_config"] } +dlp-api = { package = "magicblock-delegation-program-api", path = "dlp-api" } rand = { version = "=0.8.5", features = ["small_rng"] } +solana-pubkey = "3.0.0" solana-program-test = ">=1.16" solana-sdk = ">=1.16" tokio = { version = "^1.0", features = ["full"] } diff --git a/Makefile b/Makefile index 52a4976d..e18684a8 100644 --- a/Makefile +++ b/Makefile @@ -4,5 +4,8 @@ build: test: RUST_LOG=off cargo test-sbf --features unit_test_config +lint: + cargo clippy --features sdk,program -- -D warnings + fmt: cargo +nightly fmt diff --git a/dlp-api/Cargo.toml b/dlp-api/Cargo.toml new file mode 100644 index 00000000..cff894e5 --- /dev/null +++ b/dlp-api/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "magicblock-delegation-program-api" +version = "0.1.0" +edition = "2021" +license = "MIT" + +[lib] +name = "dlp_api" + +[features] +# Offchain encryption/decryption helpers (libsodium + solana-sdk) +encryption = ["dep:libsodium-rs", "dep:solana-sdk"] + +# Default to offchain-friendly build +default = ["encryption"] + +[dependencies] +# core program crate +dlp = { package = "magicblock-delegation-program", path = "..", default-features = false, features = ["sdk"] } + +# sdk deps +solana-program = { version = ">=1.16, <3.0.0" } +solana-sdk = { version = ">=1.16, <3.0.0", optional = true } +libsodium-rs = { version = "0.2.0", optional = true } + +borsh = { version = "1.5.3", features = ["derive"] } +bincode = { version = "^1.3" } +serde = { version = "1.0.228", default-features = false, features = ["derive"] } +thiserror = { version = ">=1" } + +[dev-dependencies] +dlp = { package = "magicblock-delegation-program", path = "..", default-features = false, features = [ + "unit_test_config" +] } +spl-token = "4.0.0" diff --git a/dlp-api/src/cpi/delegate_with_actions.rs b/dlp-api/src/cpi/delegate_with_actions.rs new file mode 100644 index 00000000..8f9833ef --- /dev/null +++ b/dlp-api/src/cpi/delegate_with_actions.rs @@ -0,0 +1,75 @@ +use borsh::to_vec; +use dlp::{ + args::{DelegateArgs, DelegateWithActionsArgs, PostDelegationActions}, + discriminator::DlpDiscriminator, + pda::{ + delegate_buffer_pda_from_delegated_account_and_owner_program, + delegation_metadata_pda_from_delegated_account, + delegation_record_pda_from_delegated_account, + }, +}; +use solana_program::{ + instruction::{AccountMeta, Instruction}, + pubkey::Pubkey, + system_program, +}; + +pub fn delegate_with_actions( + payer: Pubkey, + delegated_account: Pubkey, + owner: Option, + delegate: DelegateArgs, + actions: PostDelegationActions, +) -> Instruction { + // Note that it is OK to assume that all signers + // are readonly because DelegateWithActions needs to validate + // the signer-status, not mutability status. + let actions_signers: Vec = actions + .signers + .iter() + .copied() + .map(|pk| AccountMeta::new_readonly(pk.into(), true)) + .collect(); + + Instruction { + program_id: dlp::id(), + + accounts: { + let owner = owner.unwrap_or(system_program::id()); + let delegate_buffer_pda = + delegate_buffer_pda_from_delegated_account_and_owner_program( + &delegated_account, + &owner, + ); + let delegation_record_pda = + delegation_record_pda_from_delegated_account( + &delegated_account, + ); + let delegation_metadata_pda = + delegation_metadata_pda_from_delegated_account( + &delegated_account, + ); + + [ + vec![ + AccountMeta::new(payer, true), + AccountMeta::new(delegated_account, true), + AccountMeta::new_readonly(owner, false), + AccountMeta::new(delegate_buffer_pda, false), + AccountMeta::new(delegation_record_pda, false), + AccountMeta::new(delegation_metadata_pda, false), + AccountMeta::new_readonly(system_program::id(), false), + ], + actions_signers, + ] + .concat() + }, + + data: { + let args = DelegateWithActionsArgs { delegate, actions }; + let mut data = DlpDiscriminator::DelegateWithActions.to_vec(); + data.extend_from_slice(&to_vec(&args).unwrap()); + data + }, + } +} diff --git a/dlp-api/src/cpi/mod.rs b/dlp-api/src/cpi/mod.rs new file mode 100644 index 00000000..d9267c2b --- /dev/null +++ b/dlp-api/src/cpi/mod.rs @@ -0,0 +1,3 @@ +mod delegate_with_actions; + +pub use delegate_with_actions::*; diff --git a/dlp-api/src/decrypt.rs b/dlp-api/src/decrypt.rs new file mode 100644 index 00000000..aee3c37a --- /dev/null +++ b/dlp-api/src/decrypt.rs @@ -0,0 +1,250 @@ +use dlp::{ + args::{ + MaybeEncryptedAccountMeta, MaybeEncryptedIxData, MaybeEncryptedPubkey, + PostDelegationActions, + }, + compact, +}; +use solana_sdk::{ + instruction::{AccountMeta, Instruction}, + signer::Signer, +}; +use thiserror::Error; + +use crate::encryption::{self, EncryptionError, KEY_LEN}; + +#[derive(Debug, Error)] +pub enum DecryptError { + #[error(transparent)] + DecryptFailed(#[from] EncryptionError), + #[error("invalid decrypted pubkey length: {0}")] + InvalidPubkeyLength(usize), + #[error("invalid decrypted compact account meta length: {0}")] + InvalidAccountMetaLength(usize), + #[error("invalid decrypted compact account meta value: {0}")] + InvalidAccountMetaValue(u8), + #[error("invalid program_id index {index} for pubkey table len {len}")] + InvalidProgramIdIndex { index: u8, len: usize }, + #[error("invalid account index {index} for pubkey table len {len}")] + InvalidAccountIndex { index: u8, len: usize }, +} + +pub trait Decrypt: Sized { + type Output; + + fn decrypt( + self, + recipient_x25519_pubkey: &[u8; KEY_LEN], + recipient_x25519_secret: &[u8; KEY_LEN], + ) -> Result; + + fn decrypt_with_keypair( + self, + recipient_keypair: &solana_sdk::signature::Keypair, + ) -> Result + where + Self: Sized, + { + let recipient_x25519_secret = + encryption::keypair_to_x25519_secret(recipient_keypair)?; + let recipient_x25519_pubkey = encryption::ed25519_pubkey_to_x25519( + recipient_keypair.pubkey().as_array(), + )?; + self.decrypt(&recipient_x25519_pubkey, &recipient_x25519_secret) + } +} + +impl Decrypt for MaybeEncryptedPubkey { + type Output = [u8; 32]; + + fn decrypt( + self, + recipient_x25519_pubkey: &[u8; KEY_LEN], + recipient_x25519_secret: &[u8; KEY_LEN], + ) -> Result { + match self { + Self::ClearText(pubkey) => Ok(pubkey), + Self::Encrypted(buffer) => { + let plaintext = encryption::decrypt( + buffer.as_bytes(), + recipient_x25519_pubkey, + recipient_x25519_secret, + ) + .map_err(DecryptError::DecryptFailed)?; + Self::Output::try_from(plaintext.as_slice()).map_err(|_| { + DecryptError::InvalidPubkeyLength(plaintext.len()) + }) + } + } + } +} + +impl Decrypt for MaybeEncryptedAccountMeta { + type Output = compact::AccountMeta; + + fn decrypt( + self, + recipient_x25519_pubkey: &[u8; KEY_LEN], + recipient_x25519_secret: &[u8; KEY_LEN], + ) -> Result { + match self { + Self::ClearText(account_meta) => Ok(account_meta), + Self::Encrypted(buffer) => { + let plaintext = encryption::decrypt( + buffer.as_bytes(), + recipient_x25519_pubkey, + recipient_x25519_secret, + ) + .map_err(DecryptError::DecryptFailed)?; + if plaintext.len() != 1 { + return Err(DecryptError::InvalidAccountMetaLength( + plaintext.len(), + )); + } + compact::AccountMeta::from_byte(plaintext[0]) + .ok_or(DecryptError::InvalidAccountMetaValue(plaintext[0])) + } + } + } +} + +impl Decrypt for MaybeEncryptedIxData { + type Output = Vec; + + fn decrypt( + self, + recipient_x25519_pubkey: &[u8; KEY_LEN], + recipient_x25519_secret: &[u8; KEY_LEN], + ) -> Result { + let mut data = self.prefix; + if !self.suffix.as_bytes().is_empty() { + let suffix = encryption::decrypt( + self.suffix.as_bytes(), + recipient_x25519_pubkey, + recipient_x25519_secret, + ) + .map_err(DecryptError::DecryptFailed)?; + data.extend_from_slice(&suffix); + } + Ok(data) + } +} + +impl Decrypt for PostDelegationActions { + type Output = Vec; + + fn decrypt( + self, + recipient_x25519_pubkey: &[u8; KEY_LEN], + recipient_x25519_secret: &[u8; KEY_LEN], + ) -> Result { + let actions = self; + + let pubkeys = { + let mut pubkeys = actions.signers; + for non_signer in actions.non_signers { + pubkeys.push(non_signer.decrypt( + recipient_x25519_pubkey, + recipient_x25519_secret, + )?); + } + pubkeys + }; + + let instructions = actions + .instructions + .into_iter() + .map(|ix| { + Ok(Instruction { + program_id: pubkeys + .get(ix.program_id as usize) + .copied() + .ok_or(DecryptError::InvalidProgramIdIndex { + index: ix.program_id, + len: pubkeys.len(), + })? + .into(), + + accounts: ix + .accounts + .into_iter() + .map(|maybe_compact_meta| { + let compact_meta = maybe_compact_meta.decrypt( + recipient_x25519_pubkey, + recipient_x25519_secret, + )?; + let account_pubkey = pubkeys + .get(compact_meta.key() as usize) + .copied() + .ok_or(DecryptError::InvalidAccountIndex { + index: compact_meta.key(), + len: pubkeys.len(), + })?; + + Ok(AccountMeta { + pubkey: account_pubkey.into(), + is_signer: compact_meta.is_signer(), + is_writable: compact_meta.is_writable(), + }) + }) + .collect::, DecryptError>>()?, + + data: ix.data.decrypt( + recipient_x25519_pubkey, + recipient_x25519_secret, + )?, + }) + }) + .collect::, DecryptError>>()?; + + Ok(instructions) + } +} + +#[cfg(test)] +mod tests { + use solana_program::instruction::AccountMeta; + use solana_sdk::{pubkey::Pubkey, signature::Keypair, signer::Signer}; + + use super::*; + use crate::instruction_builder::{ + Encrypt, Encryptable, EncryptableFrom, PostDelegationInstruction, + }; + + #[test] + fn test_post_delegation_actions_decrypt_roundtrip() { + let validator = Keypair::new(); + let signer = Pubkey::new_unique(); + let nonsigner = Pubkey::new_unique(); + let program_id = Pubkey::new_unique(); + + let instructions = vec![PostDelegationInstruction { + program_id: program_id.cleartext(), + accounts: vec![ + AccountMeta::new_readonly(signer, true).cleartext(), + AccountMeta::new_readonly(nonsigner, false).encrypted(), + ], + data: vec![1, 2, 3, 4].encrypted_from(2), + }]; + + let (actions, signers) = instructions + .encrypt(&validator.pubkey()) + .expect("post-delegation actions encryption failed"); + + assert_eq!(signers, vec![AccountMeta::new_readonly(signer, true)]); + + let decrypted = actions.decrypt_with_keypair(&validator).unwrap(); + + assert_eq!( + decrypted, + vec![Instruction { + program_id, + accounts: vec![ + AccountMeta::new_readonly(signer, true), + AccountMeta::new_readonly(nonsigner, false) + ], + data: vec![1, 2, 3, 4] + }] + ); + } +} diff --git a/dlp-api/src/encrypt.rs b/dlp-api/src/encrypt.rs new file mode 100644 index 00000000..3635d2f8 --- /dev/null +++ b/dlp-api/src/encrypt.rs @@ -0,0 +1,220 @@ +use dlp::args::{ + EncryptedBuffer, MaybeEncryptedAccountMeta, MaybeEncryptedIxData, + MaybeEncryptedPubkey, +}; +use solana_program::{instruction::AccountMeta, pubkey::Pubkey}; + +use crate::{ + encryption::EncryptionError, + instruction_builder::{ + Encrypt, EncryptableAccountMeta, EncryptableIxData, EncryptablePubkey, + PostDelegationInstruction, + }, +}; + +impl Encrypt for EncryptablePubkey { + type Output = MaybeEncryptedPubkey; + type Error = EncryptionError; + + fn encrypt(self, validator: &Pubkey) -> Result { + if self.is_encryptable { + Ok(MaybeEncryptedPubkey::Encrypted(EncryptedBuffer::new( + crate::encryption::encrypt_ed25519_recipient( + self.pubkey.as_array(), + validator.as_array(), + )?, + ))) + } else { + Ok(MaybeEncryptedPubkey::ClearText(self.pubkey.to_bytes())) + } + } +} + +impl Encrypt for EncryptableIxData { + type Output = MaybeEncryptedIxData; + type Error = EncryptionError; + + fn encrypt(self, validator: &Pubkey) -> Result { + if self.encrypt_begin_offset >= self.data.len() { + Ok(MaybeEncryptedIxData { + prefix: self.data, + suffix: EncryptedBuffer::default(), + }) + } else { + Ok(MaybeEncryptedIxData { + prefix: self.data[0..self.encrypt_begin_offset].into(), + suffix: EncryptedBuffer::new( + crate::encryption::encrypt_ed25519_recipient( + &self.data[self.encrypt_begin_offset..], + validator.as_array(), + )?, + ), + }) + } + } +} + +impl Encrypt for dlp::compact::EncryptableAccountMeta { + type Output = MaybeEncryptedAccountMeta; + type Error = EncryptionError; + + fn encrypt(self, validator: &Pubkey) -> Result { + if self.is_encryptable { + Ok(MaybeEncryptedAccountMeta::Encrypted(EncryptedBuffer::new( + crate::encryption::encrypt_ed25519_recipient( + &[self.account_meta.to_byte()], + validator.as_array(), + )?, + ))) + } else { + Ok(MaybeEncryptedAccountMeta::ClearText(self.account_meta)) + } + } +} + +impl Encrypt for Vec { + type Output = (dlp::args::PostDelegationActions, Vec); + type Error = EncryptionError; + + fn encrypt(self, validator: &Pubkey) -> Result { + use dlp::args::MaybeEncryptedInstruction; + + let mut signers: Vec = Vec::new(); + + let mut add_to_signers = |meta: &EncryptableAccountMeta| { + assert!( + meta.account_meta.is_signer, + "AccountMeta must be a signer" + ); + assert!(!meta.is_encryptable, "signer must not be encryptable"); + let Some(found) = signers + .iter_mut() + .find(|m| m.pubkey == meta.account_meta.pubkey) + else { + signers.push(meta.account_meta.clone()); + return; + }; + + found.is_signer |= meta.account_meta.is_signer; + found.is_writable |= meta.account_meta.is_writable; + }; + + let mut non_signers: Vec = Vec::new(); + let mut add_to_non_signers = |meta: &EncryptableAccountMeta| { + assert!( + !meta.account_meta.is_signer, + "AccountMeta must not be a signer" + ); + let Some(found) = non_signers + .iter_mut() + .find(|m| m.account_meta.pubkey == meta.account_meta.pubkey) + else { + non_signers.push(meta.clone()); + return; + }; + + found.is_encryptable |= meta.is_encryptable; + found.account_meta.is_writable |= meta.account_meta.is_writable; + }; + + for meta in self + .iter() + .flat_map(|ix| ix.accounts.iter()) + .filter(|meta| meta.account_meta.is_signer) + { + add_to_signers(meta); + } + + for ix in self.iter() { + add_to_non_signers(&EncryptableAccountMeta { + account_meta: AccountMeta::new_readonly( + ix.program_id.pubkey, + false, + ), + is_encryptable: ix.program_id.is_encryptable, + }); + for meta in ix + .accounts + .iter() + .filter(|meta| !meta.account_meta.is_signer) + { + let Some(found) = signers + .iter_mut() + .find(|m| m.pubkey == meta.account_meta.pubkey) + else { + add_to_non_signers(meta); + continue; + }; + + found.is_writable |= meta.account_meta.is_writable; + } + } + + if signers.len() + non_signers.len() + > dlp::compact::MAX_PUBKEYS as usize + { + panic!( + "delegate_with_actions supports at most {} unique pubkeys", + dlp::compact::MAX_PUBKEYS + ); + } + + let index_of = |pk: &Pubkey| -> u8 { + if let Some(index) = signers.iter().position(|s| &s.pubkey == pk) { + return index as u8; + } + signers.len() as u8 + + non_signers + .iter() + .position(|ns| &ns.account_meta.pubkey == pk) + .expect("pubkey must exist in signers or non_signers") + as u8 + }; + + let compact_instructions: Vec = self + .into_iter() + .map(|ix| MaybeEncryptedInstruction { + program_id: index_of(&ix.program_id.pubkey), + + accounts: ix + .accounts + .into_iter() + .map(|meta| { + let index = index_of(&meta.account_meta.pubkey); + meta.to_compact(index) + .encrypt(validator) + .expect("account metadata encryption failed") + }) + .collect(), + + data: ix + .data + .encrypt(validator) + .expect("instruction data encryption failed"), + }) + .collect(); + + Ok(( + dlp::args::PostDelegationActions { + inserted_signers: 0, + inserted_non_signers: 0, + signers: signers.iter().map(|s| s.pubkey.to_bytes()).collect(), + + non_signers: non_signers + .into_iter() + .map(|ns| { + EncryptablePubkey { + pubkey: ns.account_meta.pubkey, + is_encryptable: ns.is_encryptable, + } + .encrypt(validator) + .expect("pubkey encryption failed") + }) + .collect(), + + instructions: compact_instructions, + }, + signers, + )) + } +} diff --git a/dlp-api/src/encryption/mod.rs b/dlp-api/src/encryption/mod.rs new file mode 100644 index 00000000..ee20cb2a --- /dev/null +++ b/dlp-api/src/encryption/mod.rs @@ -0,0 +1,134 @@ +use libsodium_rs::{crypto_box, crypto_sign, ensure_init}; + +pub const KEY_LEN: usize = 32; + +#[derive(Debug, thiserror::Error)] +pub enum EncryptionError { + #[error("libsodium init failed")] + SodiumInitFailed, + #[error("invalid ed25519 public key for x25519 conversion")] + InvalidEd25519PublicKey, + #[error("invalid ed25519 secret key for x25519 conversion")] + InvalidEd25519SecretKey, + #[error("invalid x25519 public key")] + InvalidX25519PublicKey, + #[error("invalid x25519 secret key")] + InvalidX25519SecretKey, + #[error("failed to encrypt payload")] + SealFailed, + #[error("failed to decrypt payload")] + OpenFailed, +} + +fn init_sodium() -> Result<(), EncryptionError> { + ensure_init().map_err(|_| EncryptionError::SodiumInitFailed)?; + Ok(()) +} + +/// Convert an Ed25519 public key into an X25519 public key. +pub fn ed25519_pubkey_to_x25519( + ed25519_pubkey: &[u8; KEY_LEN], +) -> Result<[u8; KEY_LEN], EncryptionError> { + init_sodium()?; + let ed_pk = crypto_sign::PublicKey::from_bytes(ed25519_pubkey) + .map_err(|_| EncryptionError::InvalidEd25519PublicKey)?; + let x_pk = crypto_sign::ed25519_pk_to_curve25519(&ed_pk) + .map_err(|_| EncryptionError::InvalidEd25519PublicKey)?; + let mut out = [0u8; KEY_LEN]; + out.copy_from_slice(&x_pk); + Ok(out) +} + +/// Convert an Ed25519 secret key into an X25519 secret key. +pub fn ed25519_secret_to_x25519( + ed25519_secret_key: &[u8], +) -> Result<[u8; KEY_LEN], EncryptionError> { + assert_eq!(ed25519_secret_key.len(), 64); + + init_sodium()?; + let ed_sk = crypto_sign::SecretKey::from_bytes(ed25519_secret_key) + .map_err(|_| EncryptionError::InvalidEd25519SecretKey)?; + let x_sk = crypto_sign::ed25519_sk_to_curve25519(&ed_sk) + .map_err(|_| EncryptionError::InvalidEd25519SecretKey)?; + let mut out = [0u8; KEY_LEN]; + out.copy_from_slice(&x_sk); + Ok(out) +} + +/// Convenience helper for SDK usage: derive X25519 secret key bytes from a Solana Keypair. +pub fn keypair_to_x25519_secret( + keypair: &solana_sdk::signature::Keypair, +) -> Result<[u8; KEY_LEN], EncryptionError> { + let keypair_bytes = keypair.to_bytes(); + ed25519_secret_to_x25519(&keypair_bytes) +} + +/// High-level API: encrypt for validator using sealed boxes. +pub fn encrypt_ed25519_recipient( + plaintext: &[u8], + recipient_ed25519_pubkey: &[u8; KEY_LEN], +) -> Result, EncryptionError> { + init_sodium()?; + let ed_pk = crypto_sign::PublicKey::from_bytes(recipient_ed25519_pubkey) + .map_err(|_| EncryptionError::InvalidEd25519PublicKey)?; + let x_pk = crypto_sign::ed25519_pk_to_curve25519(&ed_pk) + .map_err(|_| EncryptionError::InvalidEd25519PublicKey)?; + let x_pk = crypto_box::PublicKey::from_bytes_exact(x_pk); + crypto_box::seal_box(plaintext, &x_pk) + .map_err(|_| EncryptionError::SealFailed) +} + +/// Decrypt sealed box bytes back to plaintext bytes. +pub fn decrypt( + encrypted_payload: &[u8], + recipient_x25519_pubkey: &[u8; KEY_LEN], + recipient_x25519_secret: &[u8; KEY_LEN], +) -> Result, EncryptionError> { + init_sodium()?; + let pk = crypto_box::PublicKey::from_bytes_exact(*recipient_x25519_pubkey); + let sk = crypto_box::SecretKey::from_bytes_exact(*recipient_x25519_secret); + crypto_box::open_sealed_box(encrypted_payload, &pk, &sk) + .map_err(|_| EncryptionError::OpenFailed) +} + +#[cfg(test)] +mod tests { + use solana_sdk::signer::Signer; + + use super::*; + + #[test] + fn test_encrypt_decrypt_roundtrip() { + let validator = solana_sdk::signature::Keypair::new(); + let validator_x25519_secret = + keypair_to_x25519_secret(&validator).unwrap(); + let validator_x25519_pubkey = + ed25519_pubkey_to_x25519(validator.pubkey().as_array()).unwrap(); + let plaintext = b"hello compact actions"; + + let encrypted = + encrypt_ed25519_recipient(plaintext, validator.pubkey().as_array()) + .unwrap(); + let decrypted = decrypt( + &encrypted, + &validator_x25519_pubkey, + &validator_x25519_secret, + ) + .unwrap(); + assert_eq!(decrypted, plaintext); + } + + #[test] + fn test_random_ephemeral_changes_ciphertext() { + let validator = solana_sdk::signature::Keypair::new(); + let plaintext = b"same bytes"; + + let c1 = + encrypt_ed25519_recipient(plaintext, validator.pubkey().as_array()) + .unwrap(); + let c2 = + encrypt_ed25519_recipient(plaintext, validator.pubkey().as_array()) + .unwrap(); + assert_ne!(c1, c2); + } +} diff --git a/src/instruction_builder/call_handler.rs b/dlp-api/src/instruction_builder/call_handler.rs similarity index 95% rename from src/instruction_builder/call_handler.rs rename to dlp-api/src/instruction_builder/call_handler.rs index dba4a20a..1d247dcb 100644 --- a/src/instruction_builder/call_handler.rs +++ b/dlp-api/src/instruction_builder/call_handler.rs @@ -1,10 +1,5 @@ use borsh::to_vec; -use solana_program::{ - instruction::{AccountMeta, Instruction}, - pubkey::Pubkey, -}; - -use crate::{ +use dlp::{ args::CallHandlerArgs, discriminator::DlpDiscriminator, pda::{ @@ -13,9 +8,13 @@ use crate::{ }, total_size_budget, AccountSizeClass, DLP_PROGRAM_DATA_SIZE_CLASS, }; +use solana_program::{ + instruction::{AccountMeta, Instruction}, + pubkey::Pubkey, +}; /// Builds a call handler instruction. -/// See [crate::processor::call_handler] for docs. +/// See [dlp::processor::call_handler] for docs. #[deprecated(since = "1.1.4", note = "Use `call_handler_v2` instead")] pub fn call_handler( validator: Pubkey, @@ -41,7 +40,7 @@ pub fn call_handler( accounts.extend(other_accounts); Instruction { - program_id: crate::id(), + program_id: dlp::id(), accounts, data: [ DlpDiscriminator::CallHandler.to_vec(), diff --git a/src/instruction_builder/call_handler_v2.rs b/dlp-api/src/instruction_builder/call_handler_v2.rs similarity index 95% rename from src/instruction_builder/call_handler_v2.rs rename to dlp-api/src/instruction_builder/call_handler_v2.rs index 591fbcef..283f87b2 100644 --- a/src/instruction_builder/call_handler_v2.rs +++ b/dlp-api/src/instruction_builder/call_handler_v2.rs @@ -1,10 +1,5 @@ use borsh::to_vec; -use solana_program::{ - instruction::{AccountMeta, Instruction}, - pubkey::Pubkey, -}; - -use crate::{ +use dlp::{ args::CallHandlerArgs, discriminator::DlpDiscriminator, pda::{ @@ -13,9 +8,13 @@ use crate::{ }, total_size_budget, AccountSizeClass, DLP_PROGRAM_DATA_SIZE_CLASS, }; +use solana_program::{ + instruction::{AccountMeta, Instruction}, + pubkey::Pubkey, +}; /// Builds a call handler v2 instruction. -/// See [crate::processor::call_handler_v2] for docs. +/// See [dlp::processor::call_handler_v2] for docs. pub fn call_handler_v2( validator: Pubkey, destination_program: Pubkey, @@ -42,7 +41,7 @@ pub fn call_handler_v2( accounts.extend(other_accounts); Instruction { - program_id: crate::id(), + program_id: dlp::id(), accounts, data: [ DlpDiscriminator::CallHandlerV2.to_vec(), diff --git a/src/instruction_builder/close_ephemeral_balance.rs b/dlp-api/src/instruction_builder/close_ephemeral_balance.rs similarity index 86% rename from src/instruction_builder/close_ephemeral_balance.rs rename to dlp-api/src/instruction_builder/close_ephemeral_balance.rs index a2b3a5bd..e7a6360e 100644 --- a/src/instruction_builder/close_ephemeral_balance.rs +++ b/dlp-api/src/instruction_builder/close_ephemeral_balance.rs @@ -1,19 +1,18 @@ +use dlp::{ + discriminator::DlpDiscriminator, pda::ephemeral_balance_pda_from_payer, +}; use solana_program::{ instruction::{AccountMeta, Instruction}, pubkey::Pubkey, system_program, }; -use crate::{ - discriminator::DlpDiscriminator, pda::ephemeral_balance_pda_from_payer, -}; - /// Creates instruction to close an ephemeral balance account -/// See [crate::processor::process_close_ephemeral_balance] for docs. +/// See [dlp::processor::process_close_ephemeral_balance] for docs. pub fn close_ephemeral_balance(payer: Pubkey, index: u8) -> Instruction { let ephemeral_balance_pda = ephemeral_balance_pda_from_payer(&payer, index); Instruction { - program_id: crate::id(), + program_id: dlp::id(), accounts: vec![ AccountMeta::new(payer, true), AccountMeta::new(ephemeral_balance_pda, false), diff --git a/src/instruction_builder/close_validator_fees_vault.rs b/dlp-api/src/instruction_builder/close_validator_fees_vault.rs similarity index 88% rename from src/instruction_builder/close_validator_fees_vault.rs rename to dlp-api/src/instruction_builder/close_validator_fees_vault.rs index e489830d..626edcd6 100644 --- a/src/instruction_builder/close_validator_fees_vault.rs +++ b/dlp-api/src/instruction_builder/close_validator_fees_vault.rs @@ -1,15 +1,14 @@ +use dlp::{ + consts::DELEGATION_PROGRAM_DATA_ID, discriminator::DlpDiscriminator, + pda::validator_fees_vault_pda_from_validator, +}; use solana_program::{ instruction::{AccountMeta, Instruction}, pubkey::Pubkey, }; -use crate::{ - consts::DELEGATION_PROGRAM_DATA_ID, discriminator::DlpDiscriminator, - pda::validator_fees_vault_pda_from_validator, -}; - /// Close a validator fees vault PDA. -/// See [crate::processor::process_close_validator_fees_vault] for docs. +/// See [dlp::processor::process_close_validator_fees_vault] for docs. pub fn close_validator_fees_vault( payer: Pubkey, admin: Pubkey, @@ -19,7 +18,7 @@ pub fn close_validator_fees_vault( validator_fees_vault_pda_from_validator(&validator_identity); let delegation_program_data = DELEGATION_PROGRAM_DATA_ID.to_bytes().into(); Instruction { - program_id: crate::id(), + program_id: dlp::id(), accounts: vec![ AccountMeta::new(payer, true), AccountMeta::new(admin, true), diff --git a/src/instruction_builder/commit_diff.rs b/dlp-api/src/instruction_builder/commit_diff.rs similarity index 94% rename from src/instruction_builder/commit_diff.rs rename to dlp-api/src/instruction_builder/commit_diff.rs index f0cacc1d..bcad18ab 100644 --- a/src/instruction_builder/commit_diff.rs +++ b/dlp-api/src/instruction_builder/commit_diff.rs @@ -1,11 +1,5 @@ use borsh::to_vec; -use solana_program::{ - instruction::{AccountMeta, Instruction}, - pubkey::Pubkey, - system_program, -}; - -use crate::{ +use dlp::{ args::CommitDiffArgs, discriminator::DlpDiscriminator, pda::{ @@ -18,9 +12,14 @@ use crate::{ }, total_size_budget, AccountSizeClass, DLP_PROGRAM_DATA_SIZE_CLASS, }; +use solana_program::{ + instruction::{AccountMeta, Instruction}, + pubkey::Pubkey, + system_program, +}; /// Builds a commit state instruction. -/// See [crate::processor::fast::process_commit_diff] for docs. +/// See [dlp::processor::fast::process_commit_diff] for docs. pub fn commit_diff( validator: Pubkey, delegated_account: Pubkey, @@ -41,9 +40,9 @@ pub fn commit_diff( let program_config_pda = program_config_from_program_id(&delegated_account_owner); Instruction { - program_id: crate::id(), + program_id: dlp::id(), accounts: vec![ - AccountMeta::new_readonly(validator, true), + AccountMeta::new(validator, true), AccountMeta::new_readonly(delegated_account, false), AccountMeta::new(commit_state_pda, false), AccountMeta::new(commit_record_pda, false), diff --git a/src/instruction_builder/commit_diff_from_buffer.rs b/dlp-api/src/instruction_builder/commit_diff_from_buffer.rs similarity index 94% rename from src/instruction_builder/commit_diff_from_buffer.rs rename to dlp-api/src/instruction_builder/commit_diff_from_buffer.rs index 85aafb09..cdd8afd2 100644 --- a/src/instruction_builder/commit_diff_from_buffer.rs +++ b/dlp-api/src/instruction_builder/commit_diff_from_buffer.rs @@ -1,11 +1,5 @@ use borsh::to_vec; -use solana_program::{ - instruction::{AccountMeta, Instruction}, - pubkey::Pubkey, - system_program, -}; - -use crate::{ +use dlp::{ args::CommitStateFromBufferArgs, discriminator::DlpDiscriminator, pda::{ @@ -18,9 +12,14 @@ use crate::{ }, total_size_budget, AccountSizeClass, DLP_PROGRAM_DATA_SIZE_CLASS, }; +use solana_program::{ + instruction::{AccountMeta, Instruction}, + pubkey::Pubkey, + system_program, +}; /// Builds a commit state from buffer instruction. -/// See [crate::processor::process_commit_diff_from_buffer] for docs. +/// See [dlp::processor::process_commit_diff_from_buffer] for docs. pub fn commit_diff_from_buffer( validator: Pubkey, delegated_account: Pubkey, @@ -42,9 +41,9 @@ pub fn commit_diff_from_buffer( let program_config_pda = program_config_from_program_id(&delegated_account_owner); Instruction { - program_id: crate::id(), + program_id: dlp::id(), accounts: vec![ - AccountMeta::new_readonly(validator, true), + AccountMeta::new(validator, true), AccountMeta::new_readonly(delegated_account, false), AccountMeta::new(commit_state_pda, false), AccountMeta::new(commit_record_pda, false), diff --git a/src/instruction_builder/commit_finalize.rs b/dlp-api/src/instruction_builder/commit_finalize.rs similarity index 92% rename from src/instruction_builder/commit_finalize.rs rename to dlp-api/src/instruction_builder/commit_finalize.rs index d1d83fd5..41701d54 100644 --- a/src/instruction_builder/commit_finalize.rs +++ b/dlp-api/src/instruction_builder/commit_finalize.rs @@ -1,10 +1,4 @@ -use solana_program::{ - instruction::{AccountMeta, Instruction}, - pubkey::Pubkey, - system_program, -}; - -use crate::{ +use dlp::{ args::{CommitBumps, CommitFinalizeArgs}, delegation_metadata_seeds_from_delegated_account, delegation_record_seeds_from_delegated_account, @@ -13,6 +7,11 @@ use crate::{ total_size_budget, validator_fees_vault_seeds_from_validator, AccountSizeClass, DLP_PROGRAM_DATA_SIZE_CLASS, }; +use solana_program::{ + instruction::{AccountMeta, Instruction}, + pubkey::Pubkey, + system_program, +}; pub struct CommitPDAs { pub delegation_record: Pubkey, @@ -21,7 +20,7 @@ pub struct CommitPDAs { } /// Builds a commit finalize instruction. -/// See [crate::processor::process_commit_finalize] for docs. +/// See [dlp::processor::process_commit_finalize] for docs. pub fn commit_finalize( validator: Pubkey, delegated_account: Pubkey, @@ -30,17 +29,17 @@ pub fn commit_finalize( ) -> (Instruction, CommitPDAs) { let delegation_record = Pubkey::find_program_address( delegation_record_seeds_from_delegated_account!(delegated_account), - &crate::id(), + &dlp::id(), ); let delegation_metadata = Pubkey::find_program_address( delegation_metadata_seeds_from_delegated_account!(delegated_account), - &crate::id(), + &dlp::id(), ); let validator_fees_vault = Pubkey::find_program_address( validator_fees_vault_seeds_from_validator!(validator), - &crate::id(), + &dlp::id(), ); // save the bumps in the args @@ -52,9 +51,9 @@ pub fn commit_finalize( ( Instruction { - program_id: crate::id(), + program_id: dlp::id(), accounts: vec![ - AccountMeta::new_readonly(validator, true), + AccountMeta::new(validator, true), AccountMeta::new(delegated_account, false), AccountMeta::new_readonly(delegation_record.0, false), AccountMeta::new(delegation_metadata.0, false), diff --git a/src/instruction_builder/commit_finalize_from_buffer.rs b/dlp-api/src/instruction_builder/commit_finalize_from_buffer.rs similarity index 92% rename from src/instruction_builder/commit_finalize_from_buffer.rs rename to dlp-api/src/instruction_builder/commit_finalize_from_buffer.rs index 77439ea5..027afa80 100644 --- a/src/instruction_builder/commit_finalize_from_buffer.rs +++ b/dlp-api/src/instruction_builder/commit_finalize_from_buffer.rs @@ -1,10 +1,4 @@ -use solana_program::{ - instruction::{AccountMeta, Instruction}, - pubkey::Pubkey, - system_program, -}; - -use crate::{ +use dlp::{ args::{CommitBumps, CommitFinalizeArgs}, delegation_metadata_seeds_from_delegated_account, delegation_record_seeds_from_delegated_account, @@ -13,9 +7,14 @@ use crate::{ total_size_budget, validator_fees_vault_seeds_from_validator, AccountSizeClass, DLP_PROGRAM_DATA_SIZE_CLASS, }; +use solana_program::{ + instruction::{AccountMeta, Instruction}, + pubkey::Pubkey, + system_program, +}; /// Builds a commit state from buffer instruction. -/// See [crate::processor::process_commit_diff_from_buffer] for docs. +/// See [dlp::processor::process_commit_diff_from_buffer] for docs. pub fn commit_finalize_from_buffer( validator: Pubkey, delegated_account: Pubkey, @@ -24,17 +23,17 @@ pub fn commit_finalize_from_buffer( ) -> (Instruction, super::CommitPDAs) { let delegation_record = Pubkey::find_program_address( delegation_record_seeds_from_delegated_account!(delegated_account), - &crate::id(), + &dlp::id(), ); let validator_fees_vault = Pubkey::find_program_address( validator_fees_vault_seeds_from_validator!(validator), - &crate::id(), + &dlp::id(), ); let delegation_metadata = Pubkey::find_program_address( delegation_metadata_seeds_from_delegated_account!(delegated_account), - &crate::id(), + &dlp::id(), ); // save the bumps in the args @@ -46,9 +45,9 @@ pub fn commit_finalize_from_buffer( ( Instruction { - program_id: crate::id(), + program_id: dlp::id(), accounts: vec![ - AccountMeta::new_readonly(validator, true), + AccountMeta::new(validator, true), AccountMeta::new(delegated_account, false), AccountMeta::new_readonly(delegation_record.0, false), AccountMeta::new(delegation_metadata.0, false), diff --git a/src/instruction_builder/commit_state.rs b/dlp-api/src/instruction_builder/commit_state.rs similarity index 94% rename from src/instruction_builder/commit_state.rs rename to dlp-api/src/instruction_builder/commit_state.rs index c30eb74d..17299607 100644 --- a/src/instruction_builder/commit_state.rs +++ b/dlp-api/src/instruction_builder/commit_state.rs @@ -1,11 +1,5 @@ use borsh::to_vec; -use solana_program::{ - instruction::{AccountMeta, Instruction}, - pubkey::Pubkey, - system_program, -}; - -use crate::{ +use dlp::{ args::CommitStateArgs, discriminator::DlpDiscriminator, pda::{ @@ -18,9 +12,14 @@ use crate::{ }, total_size_budget, AccountSizeClass, DLP_PROGRAM_DATA_SIZE_CLASS, }; +use solana_program::{ + instruction::{AccountMeta, Instruction}, + pubkey::Pubkey, + system_program, +}; /// Builds a commit state instruction. -/// See [crate::processor::process_commit_state] for docs. +/// See [dlp::processor::process_commit_state] for docs. pub fn commit_state( validator: Pubkey, delegated_account: Pubkey, @@ -41,9 +40,9 @@ pub fn commit_state( let program_config_pda = program_config_from_program_id(&delegated_account_owner); Instruction { - program_id: crate::id(), + program_id: dlp::id(), accounts: vec![ - AccountMeta::new_readonly(validator, true), + AccountMeta::new(validator, true), AccountMeta::new_readonly(delegated_account, false), AccountMeta::new(commit_state_pda, false), AccountMeta::new(commit_record_pda, false), diff --git a/src/instruction_builder/commit_state_from_buffer.rs b/dlp-api/src/instruction_builder/commit_state_from_buffer.rs similarity index 94% rename from src/instruction_builder/commit_state_from_buffer.rs rename to dlp-api/src/instruction_builder/commit_state_from_buffer.rs index b455b14c..78a8b0d2 100644 --- a/src/instruction_builder/commit_state_from_buffer.rs +++ b/dlp-api/src/instruction_builder/commit_state_from_buffer.rs @@ -1,11 +1,5 @@ use borsh::to_vec; -use solana_program::{ - instruction::{AccountMeta, Instruction}, - pubkey::Pubkey, - system_program, -}; - -use crate::{ +use dlp::{ args::CommitStateFromBufferArgs, discriminator::DlpDiscriminator, pda::{ @@ -18,9 +12,14 @@ use crate::{ }, total_size_budget, AccountSizeClass, DLP_PROGRAM_DATA_SIZE_CLASS, }; +use solana_program::{ + instruction::{AccountMeta, Instruction}, + pubkey::Pubkey, + system_program, +}; /// Builds a commit state from buffer instruction. -/// See [crate::processor::process_commit_state_from_buffer] for docs. +/// See [dlp::processor::process_commit_state_from_buffer] for docs. pub fn commit_state_from_buffer( validator: Pubkey, delegated_account: Pubkey, @@ -42,9 +41,9 @@ pub fn commit_state_from_buffer( let program_config_pda = program_config_from_program_id(&delegated_account_owner); Instruction { - program_id: crate::id(), + program_id: dlp::id(), accounts: vec![ - AccountMeta::new_readonly(validator, true), + AccountMeta::new(validator, true), AccountMeta::new_readonly(delegated_account, false), AccountMeta::new(commit_state_pda, false), AccountMeta::new(commit_record_pda, false), diff --git a/src/instruction_builder/delegate.rs b/dlp-api/src/instruction_builder/delegate.rs similarity index 94% rename from src/instruction_builder/delegate.rs rename to dlp-api/src/instruction_builder/delegate.rs index 9fc5f14f..1f03fee7 100644 --- a/src/instruction_builder/delegate.rs +++ b/dlp-api/src/instruction_builder/delegate.rs @@ -1,11 +1,5 @@ use borsh::to_vec; -use solana_program::{ - instruction::{AccountMeta, Instruction}, - pubkey::Pubkey, - system_program, -}; - -use crate::{ +use dlp::{ args::DelegateArgs, discriminator::DlpDiscriminator, pda::{ @@ -15,9 +9,14 @@ use crate::{ }, total_size_budget, AccountSizeClass, DLP_PROGRAM_DATA_SIZE_CLASS, }; +use solana_program::{ + instruction::{AccountMeta, Instruction}, + pubkey::Pubkey, + system_program, +}; /// Builds a delegate instruction -/// See [crate::processor::process_delegate] for docs. +/// See [dlp::processor::process_delegate] for docs. pub fn delegate( payer: Pubkey, delegated_account: Pubkey, @@ -34,7 +33,7 @@ pub fn delegate( } /// Builds a delegate instruction that allows any validator identity. -/// See [crate::processor::process_delegate_with_any_validator] for docs. +/// See [dlp::processor::process_delegate_with_any_validator] for docs. pub fn delegate_with_any_validator( payer: Pubkey, delegated_account: Pubkey, @@ -71,7 +70,7 @@ fn build_delegate_instruction( data.extend_from_slice(&to_vec(&args).unwrap()); Instruction { - program_id: crate::id(), + program_id: dlp::id(), accounts: vec![ AccountMeta::new(payer, true), AccountMeta::new(delegated_account, true), diff --git a/src/instruction_builder/delegate_ephemeral_balance.rs b/dlp-api/src/instruction_builder/delegate_ephemeral_balance.rs similarity index 90% rename from src/instruction_builder/delegate_ephemeral_balance.rs rename to dlp-api/src/instruction_builder/delegate_ephemeral_balance.rs index e1e8f3cb..51ff287d 100644 --- a/src/instruction_builder/delegate_ephemeral_balance.rs +++ b/dlp-api/src/instruction_builder/delegate_ephemeral_balance.rs @@ -1,11 +1,5 @@ use borsh::to_vec; -use solana_program::{ - instruction::{AccountMeta, Instruction}, - pubkey::Pubkey, - system_program, -}; - -use crate::{ +use dlp::{ args::DelegateEphemeralBalanceArgs, discriminator::DlpDiscriminator, pda::{ @@ -15,9 +9,14 @@ use crate::{ ephemeral_balance_pda_from_payer, }, }; +use solana_program::{ + instruction::{AccountMeta, Instruction}, + pubkey::Pubkey, + system_program, +}; /// Delegate ephemeral balance -/// See [crate::processor::process_delegate_ephemeral_balance] for docs. +/// See [dlp::processor::process_delegate_ephemeral_balance] for docs. pub fn delegate_ephemeral_balance( payer: Pubkey, pubkey: Pubkey, @@ -38,7 +37,7 @@ pub fn delegate_ephemeral_balance( data.extend_from_slice(&to_vec(&args).unwrap()); Instruction { - program_id: crate::id(), + program_id: dlp::id(), accounts: vec![ AccountMeta::new(payer, true), AccountMeta::new_readonly(pubkey, true), @@ -47,7 +46,7 @@ pub fn delegate_ephemeral_balance( AccountMeta::new(delegation_record_pda, false), AccountMeta::new(delegation_metadata_pda, false), AccountMeta::new_readonly(system_program::id(), false), - AccountMeta::new_readonly(crate::id(), false), + AccountMeta::new_readonly(dlp::id(), false), ], data, } diff --git a/dlp-api/src/instruction_builder/delegate_with_actions.rs b/dlp-api/src/instruction_builder/delegate_with_actions.rs new file mode 100644 index 00000000..77183e37 --- /dev/null +++ b/dlp-api/src/instruction_builder/delegate_with_actions.rs @@ -0,0 +1,199 @@ +use borsh::to_vec; +use dlp::{ + args::{DelegateArgs, DelegateWithActionsArgs}, + discriminator::DlpDiscriminator, + pda::{ + delegate_buffer_pda_from_delegated_account_and_owner_program, + delegation_metadata_pda_from_delegated_account, + delegation_record_pda_from_delegated_account, + }, +}; +use solana_program::{ + instruction::{AccountMeta, Instruction}, + pubkey::Pubkey, + system_program, +}; + +use super::types::{Encrypt, PostDelegationInstruction}; + +/// See [dlp::processor::process_delegate_with_actions] for docs. +#[cfg(feature = "encryption")] +pub fn delegate_with_actions( + payer: Pubkey, + delegated_account: Pubkey, + owner: Option, + delegate: DelegateArgs, + actions: Vec, +) -> Instruction { + let encrypt_key = delegate + .validator + .expect("validator must be provided for encryption"); + let (actions, signers) = actions + .encrypt(&encrypt_key) + .expect("post-delegation actions encryption failed"); + + Instruction { + program_id: dlp::id(), + accounts: { + let owner = owner.unwrap_or(system_program::id()); + let delegate_buffer_pda = + delegate_buffer_pda_from_delegated_account_and_owner_program( + &delegated_account, + &owner, + ); + let delegation_record_pda = + delegation_record_pda_from_delegated_account( + &delegated_account, + ); + let delegation_metadata_pda = + delegation_metadata_pda_from_delegated_account( + &delegated_account, + ); + + [ + vec![ + AccountMeta::new(payer, true), + AccountMeta::new(delegated_account, true), + AccountMeta::new_readonly(owner, false), + AccountMeta::new(delegate_buffer_pda, false), + AccountMeta::new(delegation_record_pda, false), + AccountMeta::new(delegation_metadata_pda, false), + AccountMeta::new_readonly(system_program::id(), false), + ], + signers, + ] + .concat() + }, + data: { + let args = DelegateWithActionsArgs { delegate, actions }; + let mut data = DlpDiscriminator::DelegateWithActions.to_vec(); + data.extend_from_slice(&to_vec(&args).unwrap()); + data + }, + } +} + +#[cfg(test)] +#[cfg(feature = "encryption")] +mod tests { + use solana_sdk::{signature::Keypair, signer::Signer}; + + use super::*; + use crate::instruction_builder::types::{Encryptable, EncryptableFrom}; + + #[test] + fn test_compact_post_delegation_actions() { + let a = Pubkey::new_from_array([1; 32]); // 0: signer + let b = Pubkey::new_from_array([2; 32]); // 1: non-signer + let c = Pubkey::new_from_array([3; 32]); // 2: signer + let d = Pubkey::new_from_array([4; 32]); // 3: non-signer + let e = Pubkey::new_from_array([5; 32]); // 4: signer + + let instructions = vec![PostDelegationInstruction { + program_id: d.encrypted(), + accounts: vec![ + AccountMeta::new_readonly(a, true).cleartext(), // a + AccountMeta::new(c, true).cleartext(), // c + AccountMeta::new_readonly(b, false).encrypted(), // b + AccountMeta::new_readonly(e, true).cleartext(), // e + AccountMeta::new(d, false).encrypted(), // d + ], + data: vec![9].encrypted_from(1), + }]; + + let validator = Keypair::new(); + let (actions, _meta_signers) = instructions + .encrypt(&validator.pubkey()) + .expect("post-delegation actions encryption failed"); + + // indices: a, c, e, d, b + // 0, 1, 2, 3, 4 + + assert_eq!(actions.signers.len(), 3); + assert_eq!(actions.signers[0], a.to_bytes()); // signer + assert_eq!(actions.signers[1], c.to_bytes()); // signer + assert_eq!(actions.signers[2], e.to_bytes()); // signer + + assert_eq!(actions.non_signers.len(), 2); // non-signer + + // old->new mapping: a(0)->0, b(1)->4, c(2)->1, d(3)->3, e(4)->2 + assert_eq!(actions.instructions[0].program_id, 3); // d + let accounts = &actions.instructions[0].accounts; + assert!(matches!( + accounts[2], + dlp::args::MaybeEncryptedAccountMeta::Encrypted(_) + )); + assert!(matches!( + accounts[4], + dlp::args::MaybeEncryptedAccountMeta::Encrypted(_) + )); + + let dlp::args::MaybeEncryptedAccountMeta::ClearText(a_meta) = + accounts[0] + else { + panic!("expected cleartext account meta for a"); + }; + let dlp::args::MaybeEncryptedAccountMeta::ClearText(c_meta) = + accounts[1] + else { + panic!("expected cleartext account meta for c"); + }; + let dlp::args::MaybeEncryptedAccountMeta::ClearText(e_meta) = + accounts[3] + else { + panic!("expected cleartext account meta for e"); + }; + + assert_eq!(a_meta.key(), 0); // a + assert_eq!(c_meta.key(), 1); // c + assert_eq!(e_meta.key(), 2); // e + } + + #[test] + fn test_instruction_encrypted() { + let signer = Pubkey::new_unique(); + let nonsigner = Pubkey::new_unique(); + let program_id = Pubkey::new_unique(); + + let enc = Instruction { + program_id, + accounts: vec![ + AccountMeta::new_readonly(signer, true), + AccountMeta::new(nonsigner, false), + ], + data: vec![1, 2, 3], + } + .encrypted(); + + assert_eq!(enc.program_id.pubkey, program_id); + assert!(enc.program_id.is_encryptable); + assert!(enc.accounts[0].is_encryptable); + assert!(enc.accounts[1].is_encryptable); + assert_eq!(enc.data.encrypt_begin_offset, 0); + assert_eq!(enc.data.data, vec![1, 2, 3]); + } + + #[test] + fn test_instruction_encrypted_from() { + let signer = Pubkey::new_unique(); + let nonsigner = Pubkey::new_unique(); + let program_id = Pubkey::new_unique(); + + let enc = Instruction { + program_id, + accounts: vec![ + AccountMeta::new_readonly(signer, true), + AccountMeta::new(nonsigner, false), + ], + data: vec![9, 9, 9, 9, 9, 9], + } + .encrypted_from(4); + + assert_eq!(enc.program_id.pubkey, program_id); + assert!(enc.program_id.is_encryptable); + assert!(enc.accounts[0].is_encryptable); + assert!(enc.accounts[1].is_encryptable); + assert_eq!(enc.data.encrypt_begin_offset, 4); + assert_eq!(enc.data.data, vec![9, 9, 9, 9, 9, 9]); + } +} diff --git a/src/instruction_builder/finalize.rs b/dlp-api/src/instruction_builder/finalize.rs similarity index 96% rename from src/instruction_builder/finalize.rs rename to dlp-api/src/instruction_builder/finalize.rs index fe5ff0b3..bff9f77c 100644 --- a/src/instruction_builder/finalize.rs +++ b/dlp-api/src/instruction_builder/finalize.rs @@ -1,10 +1,4 @@ -use solana_program::{ - instruction::{AccountMeta, Instruction}, - pubkey::Pubkey, - system_program, -}; - -use crate::{ +use dlp::{ discriminator::DlpDiscriminator, pda::{ commit_record_pda_from_delegated_account, @@ -15,9 +9,14 @@ use crate::{ }, total_size_budget, AccountSizeClass, DLP_PROGRAM_DATA_SIZE_CLASS, }; +use solana_program::{ + instruction::{AccountMeta, Instruction}, + pubkey::Pubkey, + system_program, +}; /// Builds a finalize state instruction. -/// See [crate::processor::process_finalize] for docs. +/// See [dlp::processor::process_finalize] for docs. pub fn finalize(validator: Pubkey, delegated_account: Pubkey) -> Instruction { let commit_state_pda = commit_state_pda_from_delegated_account(&delegated_account); @@ -30,7 +29,7 @@ pub fn finalize(validator: Pubkey, delegated_account: Pubkey) -> Instruction { let validator_fees_vault_pda = validator_fees_vault_pda_from_validator(&validator); Instruction { - program_id: crate::id(), + program_id: dlp::id(), accounts: vec![ AccountMeta::new_readonly(validator, true), AccountMeta::new(delegated_account, false), diff --git a/src/instruction_builder/init_protocol_fees_vault.rs b/dlp-api/src/instruction_builder/init_protocol_fees_vault.rs similarity index 76% rename from src/instruction_builder/init_protocol_fees_vault.rs rename to dlp-api/src/instruction_builder/init_protocol_fees_vault.rs index 74b2d56e..6da69ea7 100644 --- a/src/instruction_builder/init_protocol_fees_vault.rs +++ b/dlp-api/src/instruction_builder/init_protocol_fees_vault.rs @@ -1,17 +1,16 @@ +use dlp::{discriminator::DlpDiscriminator, pda::fees_vault_pda}; use solana_program::{ instruction::{AccountMeta, Instruction}, pubkey::Pubkey, system_program, }; -use crate::{discriminator::DlpDiscriminator, pda::fees_vault_pda}; - /// Initialize the fees vault PDA. -/// See [crate::processor::process_init_protocol_fees_vault] for docs. +/// See [dlp::processor::process_init_protocol_fees_vault] for docs. pub fn init_protocol_fees_vault(payer: Pubkey) -> Instruction { let fees_vault_pda = fees_vault_pda(); Instruction { - program_id: crate::id(), + program_id: dlp::id(), accounts: vec![ AccountMeta::new(payer, true), AccountMeta::new(fees_vault_pda, false), diff --git a/src/instruction_builder/init_validator_fees_vault.rs b/dlp-api/src/instruction_builder/init_validator_fees_vault.rs similarity index 89% rename from src/instruction_builder/init_validator_fees_vault.rs rename to dlp-api/src/instruction_builder/init_validator_fees_vault.rs index c5601892..07e2d5cc 100644 --- a/src/instruction_builder/init_validator_fees_vault.rs +++ b/dlp-api/src/instruction_builder/init_validator_fees_vault.rs @@ -1,16 +1,15 @@ +use dlp::{ + consts::DELEGATION_PROGRAM_DATA_ID, discriminator::DlpDiscriminator, + pda::validator_fees_vault_pda_from_validator, +}; use solana_program::{ instruction::{AccountMeta, Instruction}, pubkey::Pubkey, system_program, }; -use crate::{ - consts::DELEGATION_PROGRAM_DATA_ID, discriminator::DlpDiscriminator, - pda::validator_fees_vault_pda_from_validator, -}; - /// Initialize a validator fees vault PDA. -/// See [crate::processor::process_init_validator_fees_vault] for docs. +/// See [dlp::processor::process_init_validator_fees_vault] for docs. pub fn init_validator_fees_vault( payer: Pubkey, admin: Pubkey, @@ -20,7 +19,7 @@ pub fn init_validator_fees_vault( validator_fees_vault_pda_from_validator(&validator_identity); let delegation_program_data = DELEGATION_PROGRAM_DATA_ID.to_bytes().into(); Instruction { - program_id: crate::id(), + program_id: dlp::id(), accounts: vec![ AccountMeta::new(payer, true), AccountMeta::new(admin, true), diff --git a/src/instruction_builder/mod.rs b/dlp-api/src/instruction_builder/mod.rs similarity index 93% rename from src/instruction_builder/mod.rs rename to dlp-api/src/instruction_builder/mod.rs index 66eef03b..ba81eb84 100644 --- a/src/instruction_builder/mod.rs +++ b/dlp-api/src/instruction_builder/mod.rs @@ -10,11 +10,13 @@ mod commit_state; mod commit_state_from_buffer; mod delegate; mod delegate_ephemeral_balance; +mod delegate_with_actions; mod finalize; mod init_protocol_fees_vault; mod init_validator_fees_vault; mod protocol_claim_fees; mod top_up_ephemeral_balance; +mod types; mod undelegate; mod undelegate_confined_account; mod validator_claim_fees; @@ -32,11 +34,13 @@ pub use commit_state::*; pub use commit_state_from_buffer::*; pub use delegate::*; pub use delegate_ephemeral_balance::*; +pub use delegate_with_actions::*; pub use finalize::*; pub use init_protocol_fees_vault::*; pub use init_validator_fees_vault::*; pub use protocol_claim_fees::*; pub use top_up_ephemeral_balance::*; +pub use types::*; pub use undelegate::*; pub use undelegate_confined_account::*; pub use validator_claim_fees::*; diff --git a/src/instruction_builder/protocol_claim_fees.rs b/dlp-api/src/instruction_builder/protocol_claim_fees.rs similarity index 86% rename from src/instruction_builder/protocol_claim_fees.rs rename to dlp-api/src/instruction_builder/protocol_claim_fees.rs index cbd483ff..98232a9e 100644 --- a/src/instruction_builder/protocol_claim_fees.rs +++ b/dlp-api/src/instruction_builder/protocol_claim_fees.rs @@ -1,20 +1,19 @@ +use dlp::{ + consts::DELEGATION_PROGRAM_DATA_ID, discriminator::DlpDiscriminator, + pda::fees_vault_pda, +}; use solana_program::{ instruction::{AccountMeta, Instruction}, pubkey::Pubkey, }; -use crate::{ - consts::DELEGATION_PROGRAM_DATA_ID, discriminator::DlpDiscriminator, - pda::fees_vault_pda, -}; - /// Claim the accrued fees from the protocol fees vault. -/// See [crate::processor::process_protocol_claim_fees] for docs. +/// See [dlp::processor::process_protocol_claim_fees] for docs. pub fn protocol_claim_fees(admin: Pubkey) -> Instruction { let fees_vault_pda = fees_vault_pda(); let delegation_program_data = DELEGATION_PROGRAM_DATA_ID.to_bytes().into(); Instruction { - program_id: crate::id(), + program_id: dlp::id(), accounts: vec![ AccountMeta::new(admin, true), AccountMeta::new(fees_vault_pda, false), diff --git a/src/instruction_builder/top_up_ephemeral_balance.rs b/dlp-api/src/instruction_builder/top_up_ephemeral_balance.rs similarity index 90% rename from src/instruction_builder/top_up_ephemeral_balance.rs rename to dlp-api/src/instruction_builder/top_up_ephemeral_balance.rs index bc44a290..d64fa819 100644 --- a/src/instruction_builder/top_up_ephemeral_balance.rs +++ b/dlp-api/src/instruction_builder/top_up_ephemeral_balance.rs @@ -1,17 +1,16 @@ use borsh::to_vec; +use dlp::{ + args::TopUpEphemeralBalanceArgs, discriminator::DlpDiscriminator, + pda::ephemeral_balance_pda_from_payer, +}; use solana_program::{ instruction::{AccountMeta, Instruction}, pubkey::Pubkey, system_program, }; -use crate::{ - args::TopUpEphemeralBalanceArgs, discriminator::DlpDiscriminator, - pda::ephemeral_balance_pda_from_payer, -}; - /// Builds a top-up ephemeral balance instruction. -/// See [crate::processor::process_top_up_ephemeral_balance] for docs. +/// See [dlp::processor::process_top_up_ephemeral_balance] for docs. pub fn top_up_ephemeral_balance( payer: Pubkey, pubkey: Pubkey, @@ -25,7 +24,7 @@ pub fn top_up_ephemeral_balance( let ephemeral_balance_pda = ephemeral_balance_pda_from_payer(&pubkey, args.index); Instruction { - program_id: crate::id(), + program_id: dlp::id(), accounts: vec![ AccountMeta::new(payer, true), AccountMeta::new_readonly(pubkey, false), diff --git a/dlp-api/src/instruction_builder/types/encryptable_types.rs b/dlp-api/src/instruction_builder/types/encryptable_types.rs new file mode 100644 index 00000000..f1add427 --- /dev/null +++ b/dlp-api/src/instruction_builder/types/encryptable_types.rs @@ -0,0 +1,182 @@ +use solana_program::{ + instruction::{AccountMeta, Instruction}, + pubkey::Pubkey, +}; + +use crate::instruction_builder::{Encryptable, EncryptableFrom}; + +/// PostDelegationInstruction + Encryptable +pub struct PostDelegationInstruction { + pub program_id: EncryptablePubkey, + pub accounts: Vec, + pub data: EncryptableIxData, +} + +/// Instruction is never encrypted and only its parts are encrypted; +/// and this Encryptable implementation is a shorthand for calling +/// encrypted() and encrypted_from(0) on all its parts. +impl Encryptable for Instruction { + type Output = PostDelegationInstruction; + fn with_encryption(self, encrypt: bool) -> Self::Output { + if encrypt { + PostDelegationInstruction { + program_id: self.program_id.encrypted(), + accounts: self + .accounts + .into_iter() + .map(|m| m.encrypted()) + .collect(), + data: self.data.encrypted_from(0), + } + } else { + PostDelegationInstruction { + program_id: self.program_id.cleartext(), + accounts: self + .accounts + .into_iter() + .map(|m| m.cleartext()) + .collect(), + data: self.data.encrypted_from(usize::MAX), + } + } + } +} + +/// Instruction is never encrypted and only its parts are encrypted; +/// and this Encryptable implementation is a shorthand for calling +/// encrypted() and encrypted_from(offset) on all its parts. +impl EncryptableFrom for Instruction { + type Output = PostDelegationInstruction; + fn encrypted_from(self, offset: usize) -> Self::Output { + PostDelegationInstruction { + program_id: self.program_id.encrypted(), + accounts: self + .accounts + .into_iter() + .map(|m| m.encrypted()) + .collect(), + data: self.data.encrypted_from(offset), + } + } +} + +/// EncryptablePubkey + Encryptable +#[derive(Clone, Debug)] +pub struct EncryptablePubkey { + pub pubkey: Pubkey, + pub is_encryptable: bool, +} + +impl Encryptable for Pubkey { + type Output = EncryptablePubkey; + fn with_encryption(self, encrypt: bool) -> Self::Output { + EncryptablePubkey { + pubkey: self, + is_encryptable: encrypt, + } + } +} + +/// EncryptableAccountMeta + Encryptable +// NOTE: This type is not encrypted directly. We first convert it to its +// compact::EncryptableAccountMeta which gets encrypted. +#[derive(Clone, Debug)] +pub struct EncryptableAccountMeta { + pub account_meta: AccountMeta, + pub is_encryptable: bool, +} + +impl EncryptableAccountMeta { + pub fn to_compact(self, index: u8) -> dlp::compact::EncryptableAccountMeta { + dlp::compact::EncryptableAccountMeta { + account_meta: dlp::compact::AccountMeta::try_new( + index, + self.account_meta.is_signer, + self.account_meta.is_writable, + ) + .expect("compact account index must fit in 6 bits"), + is_encryptable: self.is_encryptable, + } + } +} + +impl Encryptable for AccountMeta { + type Output = EncryptableAccountMeta; + fn with_encryption(self, encrypt: bool) -> Self::Output { + EncryptableAccountMeta { + account_meta: self, + is_encryptable: encrypt, + } + } +} + +/// EncryptableIxData + EncryptableFrom +#[derive(Clone, Debug)] +pub struct EncryptableIxData { + pub data: Vec, + + /// [0, encrypt_offset) is cleartext and [encrypt_offset, len) is encrypted + pub encrypt_begin_offset: usize, +} + +impl EncryptableFrom for Vec { + type Output = EncryptableIxData; + fn encrypted_from(self, offset: usize) -> Self::Output { + EncryptableIxData { + encrypt_begin_offset: offset.min(self.len()), + data: self, + } + } +} + +/// +/// PostDelegationInstruction { +/// program_id: pubkey +/// accounts: Vec, +/// encrypted_accounts: EncryptedVec, +/// data: Vec +/// encryptedData: Vec +/// } +/// +#[test] +fn dev_experience() { + const USDC_SCALE: u64 = 1000_000; + + use solana_program::{instruction::AccountMeta, pubkey::Pubkey}; + use spl_token::instruction::TokenInstruction; + + let sender = Pubkey::new_unique(); + let recipient = Pubkey::new_unique(); + let authority = Pubkey::new_unique(); + let amount: u64 = 100 * USDC_SCALE; // 100 USDC with 6 decimals + + let regular_transfer_ix = Instruction { + program_id: spl_token::id(), + accounts: vec![ + AccountMeta::new(sender, false), + AccountMeta::new(recipient, false), + AccountMeta::new_readonly(authority, true), + ], + data: TokenInstruction::Transfer { amount }.pack(), + }; + + // Use: + // - encrypted() and encrypted_from() to make parts private + // - cleartext() for public + let private_transfer_ix = PostDelegationInstruction { + program_id: spl_token::id().cleartext(), + accounts: vec![ + AccountMeta::new(sender, false).cleartext(), + AccountMeta::new(recipient, false).encrypted(), + AccountMeta::new_readonly(authority, true).cleartext(), + ], + data: TokenInstruction::Transfer { amount } + .pack() + .encrypted_from(1), + }; + + assert_eq!(regular_transfer_ix.program_id, spl_token::id()); + assert_eq!(private_transfer_ix.program_id.pubkey, spl_token::id()); + assert!(private_transfer_ix.accounts[1].is_encryptable); + assert_eq!(private_transfer_ix.data.encrypt_begin_offset, 1); +} diff --git a/dlp-api/src/instruction_builder/types/mod.rs b/dlp-api/src/instruction_builder/types/mod.rs new file mode 100644 index 00000000..8ce21c79 --- /dev/null +++ b/dlp-api/src/instruction_builder/types/mod.rs @@ -0,0 +1,27 @@ +mod encryptable_types; + +pub use encryptable_types::*; +use solana_program::pubkey::Pubkey; + +pub trait Encryptable: Sized { + type Output; + fn encrypted(self) -> Self::Output { + self.with_encryption(true) + } + fn cleartext(self) -> Self::Output { + self.with_encryption(false) + } + fn with_encryption(self, encrypt: bool) -> Self::Output; +} + +pub trait EncryptableFrom: Sized { + type Output; + fn encrypted_from(self, offset: usize) -> Self::Output; +} + +pub trait Encrypt: Sized { + type Output; + type Error; + + fn encrypt(self, validator: &Pubkey) -> Result; +} diff --git a/src/instruction_builder/undelegate.rs b/dlp-api/src/instruction_builder/undelegate.rs similarity index 96% rename from src/instruction_builder/undelegate.rs rename to dlp-api/src/instruction_builder/undelegate.rs index bc0c4692..bea749de 100644 --- a/src/instruction_builder/undelegate.rs +++ b/dlp-api/src/instruction_builder/undelegate.rs @@ -1,10 +1,4 @@ -use solana_program::{ - instruction::{AccountMeta, Instruction}, - pubkey::Pubkey, - system_program, -}; - -use crate::{ +use dlp::{ discriminator::DlpDiscriminator, pda::{ commit_record_pda_from_delegated_account, @@ -16,9 +10,14 @@ use crate::{ }, total_size_budget, AccountSizeClass, DLP_PROGRAM_DATA_SIZE_CLASS, }; +use solana_program::{ + instruction::{AccountMeta, Instruction}, + pubkey::Pubkey, + system_program, +}; /// Builds an undelegate instruction. -/// See [crate::processor::process_undelegate] for docs. +/// See [dlp::processor::process_undelegate] for docs. #[allow(clippy::too_many_arguments)] pub fn undelegate( validator: Pubkey, @@ -40,7 +39,7 @@ pub fn undelegate( let validator_fees_vault_pda = validator_fees_vault_pda_from_validator(&validator); Instruction { - program_id: crate::id(), + program_id: dlp::id(), accounts: vec![ AccountMeta::new(validator, true), AccountMeta::new(delegated_account, false), diff --git a/src/instruction_builder/undelegate_confined_account.rs b/dlp-api/src/instruction_builder/undelegate_confined_account.rs similarity index 92% rename from src/instruction_builder/undelegate_confined_account.rs rename to dlp-api/src/instruction_builder/undelegate_confined_account.rs index 8a1ccd0a..d4a45a33 100644 --- a/src/instruction_builder/undelegate_confined_account.rs +++ b/dlp-api/src/instruction_builder/undelegate_confined_account.rs @@ -1,10 +1,4 @@ -use solana_program::{ - instruction::{AccountMeta, Instruction}, - pubkey::Pubkey, - system_program, -}; - -use crate::{ +use dlp::{ consts::DELEGATION_PROGRAM_DATA_ID, discriminator::DlpDiscriminator, pda::{ @@ -13,9 +7,14 @@ use crate::{ undelegate_buffer_pda_from_delegated_account, }, }; +use solana_program::{ + instruction::{AccountMeta, Instruction}, + pubkey::Pubkey, + system_program, +}; /// Builds an admin-only undelegate instruction for confined accounts. -/// See [crate::processor::process_undelegate_confined_account] for docs. +/// See [dlp::processor::process_undelegate_confined_account] for docs. pub fn undelegate_confined_account( admin: Pubkey, delegated_account: Pubkey, @@ -30,7 +29,7 @@ pub fn undelegate_confined_account( let delegation_program_data = DELEGATION_PROGRAM_DATA_ID.to_bytes().into(); Instruction { - program_id: crate::id(), + program_id: dlp::id(), accounts: vec![ AccountMeta::new(admin, true), AccountMeta::new(delegated_account, false), diff --git a/src/instruction_builder/validator_claim_fees.rs b/dlp-api/src/instruction_builder/validator_claim_fees.rs similarity index 89% rename from src/instruction_builder/validator_claim_fees.rs rename to dlp-api/src/instruction_builder/validator_claim_fees.rs index e49f642e..efb0709d 100644 --- a/src/instruction_builder/validator_claim_fees.rs +++ b/dlp-api/src/instruction_builder/validator_claim_fees.rs @@ -1,17 +1,16 @@ use borsh::to_vec; -use solana_program::{ - instruction::{AccountMeta, Instruction}, - pubkey::Pubkey, -}; - -use crate::{ +use dlp::{ args::ValidatorClaimFeesArgs, discriminator::DlpDiscriminator, pda::{fees_vault_pda, validator_fees_vault_pda_from_validator}, }; +use solana_program::{ + instruction::{AccountMeta, Instruction}, + pubkey::Pubkey, +}; /// Claim the accrued fees from the fees vault. -/// See [crate::processor::process_validator_claim_fees] for docs. +/// See [dlp::processor::process_validator_claim_fees] for docs. pub fn validator_claim_fees( validator: Pubkey, amount: Option, @@ -21,7 +20,7 @@ pub fn validator_claim_fees( let validator_fees_vault_pda = validator_fees_vault_pda_from_validator(&validator); Instruction { - program_id: crate::id(), + program_id: dlp::id(), accounts: vec![ AccountMeta::new(validator, true), AccountMeta::new(fees_vault_pda, false), diff --git a/src/instruction_builder/whitelist_validator_for_program.rs b/dlp-api/src/instruction_builder/whitelist_validator_for_program.rs similarity index 92% rename from src/instruction_builder/whitelist_validator_for_program.rs rename to dlp-api/src/instruction_builder/whitelist_validator_for_program.rs index 204b0a2a..30ff7544 100644 --- a/src/instruction_builder/whitelist_validator_for_program.rs +++ b/dlp-api/src/instruction_builder/whitelist_validator_for_program.rs @@ -1,4 +1,8 @@ use borsh::to_vec; +use dlp::{ + args::WhitelistValidatorForProgramArgs, consts::DELEGATION_PROGRAM_DATA_ID, + discriminator::DlpDiscriminator, pda::program_config_from_program_id, +}; use solana_program::{ bpf_loader_upgradeable, instruction::{AccountMeta, Instruction}, @@ -6,14 +10,9 @@ use solana_program::{ system_program, }; -use crate::{ - args::WhitelistValidatorForProgramArgs, consts::DELEGATION_PROGRAM_DATA_ID, - discriminator::DlpDiscriminator, pda::program_config_from_program_id, -}; - /// Whitelist validator for program /// -/// See [crate::processor::process_whitelist_validator_for_program] for docs. +/// See [dlp::processor::process_whitelist_validator_for_program] for docs. pub fn whitelist_validator_for_program( authority: Pubkey, validator_identity: Pubkey, @@ -29,7 +28,7 @@ pub fn whitelist_validator_for_program( let delegation_program_data = DELEGATION_PROGRAM_DATA_ID.to_bytes().into(); let program_config_pda = program_config_from_program_id(&program); Instruction { - program_id: crate::id(), + program_id: dlp::id(), accounts: vec![ AccountMeta::new(authority, true), AccountMeta::new_readonly(validator_identity, false), diff --git a/dlp-api/src/lib.rs b/dlp-api/src/lib.rs new file mode 100644 index 00000000..c1d4a83f --- /dev/null +++ b/dlp-api/src/lib.rs @@ -0,0 +1,17 @@ +pub use dlp; + +pub mod instruction_builder; + +pub mod cpi; + +#[cfg(feature = "encryption")] +pub mod decrypt; + +#[cfg(feature = "encryption")] +pub mod encrypt; + +#[cfg(feature = "encryption")] +pub mod encryption; + +#[cfg(feature = "encryption")] +pub use decrypt::*; diff --git a/src/args/delegate_with_actions.rs b/src/args/delegate_with_actions.rs new file mode 100644 index 00000000..02021685 --- /dev/null +++ b/src/args/delegate_with_actions.rs @@ -0,0 +1,102 @@ +use borsh::{BorshDeserialize, BorshSerialize}; + +use super::DelegateArgs; +use crate::compact; + +#[derive(Debug, BorshSerialize, BorshDeserialize)] +pub struct DelegateWithActionsArgs { + /// Standard delegation parameters. + pub delegate: DelegateArgs, + + /// Compact post-delegation actions. + pub actions: PostDelegationActions, +} + +#[derive(Debug, BorshSerialize, BorshDeserialize)] +pub struct PostDelegationActions { + pub inserted_signers: u8, + + pub inserted_non_signers: u8, + + pub signers: Vec<[u8; 32]>, + + pub non_signers: Vec, + + pub instructions: Vec, +} + +#[derive(Clone, Debug, BorshSerialize, BorshDeserialize)] +pub struct MaybeEncryptedInstruction { + pub program_id: u8, + + pub accounts: Vec, + + pub data: MaybeEncryptedIxData, +} + +#[derive(Clone, Debug, BorshSerialize, BorshDeserialize)] +#[cfg_attr(test, derive(PartialEq))] +pub enum MaybeEncryptedPubkey { + ClearText([u8; 32]), + Encrypted(EncryptedBuffer), +} + +impl From<[u8; 32]> for MaybeEncryptedPubkey { + fn from(pubkey: [u8; 32]) -> Self { + Self::ClearText(pubkey) + } +} + +impl From> for MaybeEncryptedPubkey { + fn from(bytes: Vec) -> Self { + Self::Encrypted(bytes.into()) + } +} + +#[derive(Clone, Debug, BorshSerialize, BorshDeserialize)] +pub enum MaybeEncryptedAccountMeta { + ClearText(compact::AccountMeta), + Encrypted(EncryptedBuffer), +} + +impl From for MaybeEncryptedAccountMeta { + fn from(account_meta: compact::AccountMeta) -> Self { + Self::ClearText(account_meta) + } +} + +impl From> for MaybeEncryptedAccountMeta { + fn from(bytes: Vec) -> Self { + Self::Encrypted(bytes.into()) + } +} + +#[derive(Clone, Debug, BorshSerialize, BorshDeserialize)] +pub struct MaybeEncryptedIxData { + pub prefix: Vec, + pub suffix: EncryptedBuffer, +} + +#[derive(Clone, Debug, Default, BorshSerialize, BorshDeserialize)] +#[cfg_attr(test, derive(PartialEq))] +pub struct EncryptedBuffer(Vec); + +impl EncryptedBuffer { + pub fn new(bytes: Vec) -> Self { + Self(bytes) + } + + pub fn as_bytes(&self) -> &[u8] { + &self.0 + } + + pub fn into_inner(self) -> Vec { + self.0 + } +} + +impl From> for EncryptedBuffer { + fn from(bytes: Vec) -> Self { + Self(bytes) + } +} diff --git a/src/args/mod.rs b/src/args/mod.rs index 89853b11..492f02f4 100644 --- a/src/args/mod.rs +++ b/src/args/mod.rs @@ -2,6 +2,7 @@ mod call_handler; mod commit_state; mod delegate; mod delegate_ephemeral_balance; +mod delegate_with_actions; mod top_up_ephemeral_balance; mod types; mod validator_claim_fees; @@ -11,6 +12,7 @@ pub use call_handler::*; pub use commit_state::*; pub use delegate::*; pub use delegate_ephemeral_balance::*; +pub use delegate_with_actions::*; pub use top_up_ephemeral_balance::*; pub use types::*; pub use validator_claim_fees::*; diff --git a/src/compact/account_meta.rs b/src/compact/account_meta.rs new file mode 100644 index 00000000..87ab821c --- /dev/null +++ b/src/compact/account_meta.rs @@ -0,0 +1,110 @@ +use borsh::{ + io::{Read, Write}, + BorshDeserialize, BorshSerialize, +}; +use serde::{Deserialize, Serialize}; + +use crate::{args::MaybeEncryptedAccountMeta, compact::ClearText}; + +const ACCOUNT_INDEX_MASK: u8 = 0b0011_1111; +const SIGNER_MASK: u8 = 0b0100_0000; +const WRITABLE_MASK: u8 = 0b1000_0000; + +/// +/// MAX_PUBKEYS = 64 +/// +pub const MAX_PUBKEYS: u8 = ACCOUNT_INDEX_MASK + 1; + +/// Compact account meta packed into one byte. +/// Bits `0..=5` encode the pubkey-table index (`0..MAX_PUBKEYS-1`). +/// Bit `6` is `is_signer`, and bit `7` is `is_writable`. +#[derive(Clone, Copy, Debug, Serialize, Deserialize)] +pub struct AccountMeta(u8); + +impl BorshSerialize for AccountMeta { + fn serialize( + &self, + writer: &mut W, + ) -> Result<(), borsh::io::Error> { + BorshSerialize::serialize(&self.0, writer) + } +} + +impl BorshDeserialize for AccountMeta { + fn deserialize_reader( + reader: &mut R, + ) -> Result { + let value = u8::deserialize_reader(reader)?; + Ok(Self(value)) + } +} + +impl AccountMeta { + pub fn new(index: u8, is_signer: bool) -> Self { + Self::try_new(index, is_signer, true).expect("index is out of range") + } + pub fn new_readonly(index: u8, is_signer: bool) -> Self { + Self::try_new(index, is_signer, false).expect("index is out of range") + } + + pub fn try_new( + index: u8, + is_signer: bool, + is_writable: bool, + ) -> Option { + if index >= MAX_PUBKEYS { + return None; + } + let mut packed = index; + if is_signer { + packed |= SIGNER_MASK; + } + if is_writable { + packed |= WRITABLE_MASK; + } + Some(Self(packed)) + } + + pub fn key(self) -> u8 { + self.0 & ACCOUNT_INDEX_MASK + } + + pub fn is_signer(self) -> bool { + (self.0 & SIGNER_MASK) != 0 + } + + pub fn is_writable(self) -> bool { + (self.0 & WRITABLE_MASK) != 0 + } + + pub fn set_index(&mut self, new_index: u8) { + *self = Self::try_new(new_index, self.is_signer(), self.is_writable()) + .expect("index is out of range"); + } + + pub fn to_byte(self) -> u8 { + self.0 + } + + pub fn from_byte(value: u8) -> Option { + Self::try_new( + value & ACCOUNT_INDEX_MASK, + (value & SIGNER_MASK) != 0, + (value & WRITABLE_MASK) != 0, + ) + } +} + +impl ClearText for AccountMeta { + type Output = MaybeEncryptedAccountMeta; + + fn cleartext(self) -> Self::Output { + MaybeEncryptedAccountMeta::ClearText(self) + } +} + +#[derive(Clone, Copy, Debug, Serialize, Deserialize)] +pub struct EncryptableAccountMeta { + pub account_meta: AccountMeta, + pub is_encryptable: bool, +} diff --git a/src/compact/instruction.rs b/src/compact/instruction.rs new file mode 100644 index 00000000..a6cae660 --- /dev/null +++ b/src/compact/instruction.rs @@ -0,0 +1,55 @@ +use serde::{Deserialize, Serialize}; + +use crate::{args::MaybeEncryptedInstruction, compact, compact::ClearText}; + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct Instruction { + pub program_id: u8, + pub accounts: Vec, + pub data: Vec, +} + +impl Instruction { + pub fn from_instruction( + ix: solana_program::instruction::Instruction, + index_of: &mut impl FnMut( + /*account_key*/ solana_program::pubkey::Pubkey, + /*signer*/ bool, + ) -> u8, + ) -> Instruction { + Instruction { + program_id: index_of(ix.program_id, false), + + accounts: ix + .accounts + .iter() + .map(|meta| { + compact::AccountMeta::try_new( + index_of(meta.pubkey, meta.is_signer), + meta.is_signer, + meta.is_writable, + ) + .expect("compact account index must fit in 6 bits") + }) + .collect(), + + data: ix.data, + } + } +} + +impl ClearText for Instruction { + type Output = MaybeEncryptedInstruction; + + fn cleartext(self) -> Self::Output { + MaybeEncryptedInstruction { + program_id: self.program_id, + accounts: self + .accounts + .into_iter() + .map(|meta| meta.cleartext()) + .collect(), + data: self.data.cleartext(), + } + } +} diff --git a/src/compact/mod.rs b/src/compact/mod.rs new file mode 100644 index 00000000..d8fca59b --- /dev/null +++ b/src/compact/mod.rs @@ -0,0 +1,463 @@ +mod account_meta; +mod instruction; + +pub use account_meta::*; +pub use instruction::*; +use pinocchio::Address; + +use crate::args::{ + EncryptedBuffer, MaybeEncryptedInstruction, MaybeEncryptedIxData, + MaybeEncryptedPubkey, PostDelegationActions, +}; + +pub trait ClearText: Sized { + type Output; + + fn cleartext(self) -> Self::Output; +} + +pub trait ClearTextWithInsertable: Sized { + type Output; + + fn cleartext_with_insertable( + self, + insertable: PostDelegationActions, + insert_before_index: usize, + ) -> Self::Output; +} + +impl ClearText for Vec { + type Output = MaybeEncryptedIxData; + + fn cleartext(self) -> Self::Output { + MaybeEncryptedIxData { + prefix: self, + suffix: EncryptedBuffer::default(), + } + } +} + +impl ClearText for Vec { + type Output = PostDelegationActions; + + fn cleartext(self) -> Self::Output { + let mut signers: Vec = Vec::new(); + let mut non_signers: Vec = Vec::new(); + + let mut add_to_signers = |meta: &solana_instruction::AccountMeta| { + assert!(meta.is_signer, "AccountMeta must be a signer"); + let Some(found) = + signers.iter_mut().find(|m| m.pubkey == meta.pubkey) + else { + signers.push(meta.clone()); + return; + }; + + found.is_signer |= meta.is_signer; + found.is_writable |= meta.is_writable; + }; + + let mut add_to_non_signers = + |meta: &solana_instruction::AccountMeta| { + assert!(!meta.is_signer, "AccountMeta must not be a signer"); + let Some(found) = + non_signers.iter_mut().find(|m| m.pubkey == meta.pubkey) + else { + non_signers.push(meta.clone()); + return; + }; + + found.is_writable |= meta.is_writable; + }; + + for meta in self + .iter() + .flat_map(|ix| ix.accounts.iter()) + .filter(|meta| meta.is_signer) + { + add_to_signers(meta); + } + + for ix in self.iter() { + add_to_non_signers(&solana_instruction::AccountMeta::new_readonly( + ix.program_id, + false, + )); + for meta in ix.accounts.iter().filter(|meta| !meta.is_signer) { + let Some(found) = + signers.iter_mut().find(|m| m.pubkey == meta.pubkey) + else { + add_to_non_signers(meta); + continue; + }; + + found.is_writable |= meta.is_writable; + } + } + + if signers.len() + non_signers.len() + > crate::compact::MAX_PUBKEYS as usize + { + panic!( + "delegate_with_actions supports at most {} unique pubkeys", + crate::compact::MAX_PUBKEYS + ); + } + + let index_of = |pk: &solana_address::Address| -> u8 { + if let Some(index) = signers.iter().position(|s| &s.pubkey == pk) { + return index as u8; + } + signers.len() as u8 + + non_signers + .iter() + .position(|ns| &ns.pubkey == pk) + .expect("pubkey must exist in signers or non_signers") + as u8 + }; + + let compact_instructions: Vec = self + .into_iter() + .map(|ix| MaybeEncryptedInstruction { + program_id: index_of(&ix.program_id), + + accounts: ix + .accounts + .into_iter() + .map(|meta| { + let index = index_of(&meta.pubkey); + crate::compact::AccountMeta::try_new( + index, + meta.is_signer, + meta.is_writable, + ) + .expect("compact account index must fit in 6 bits") + .cleartext() + }) + .collect(), + + data: ix.data.cleartext(), + }) + .collect(); + + PostDelegationActions { + inserted_signers: 0, + inserted_non_signers: 0, + + signers: signers.iter().map(|s| s.pubkey.to_bytes()).collect(), + + non_signers: non_signers + .into_iter() + .map(|ns| MaybeEncryptedPubkey::ClearText(ns.pubkey.to_bytes())) + .collect(), + + instructions: compact_instructions, + } + } +} + +impl ClearTextWithInsertable for Vec { + type Output = PostDelegationActions; + fn cleartext_with_insertable( + self, + insertable: PostDelegationActions, + insert_before_index: usize, + ) -> Self::Output { + assert!( + insertable.inserted_signers == 0, + "PostDelegationActions does not support multiple merge/insert" + ); + assert!( + insertable.inserted_non_signers == 0, + "PostDelegationActions does not support multiple merge/insert" + ); + + // add keys from actions (pre-encrypted instructions) + let mut skipable_pubkeys: Vec> = vec![]; + { + for signer in insertable.signers.iter() { + skipable_pubkeys.push(Some((*signer).into())); + } + for non_signer in insertable.non_signers.iter() { + if let MaybeEncryptedPubkey::ClearText(non_signer) = non_signer + { + skipable_pubkeys.push(Some((*non_signer).into())); + } else { + // Note that None is added to the list, to mark that this slot is encrypted but + // the index is already taken so that the index in referred by insertable.instructions + // is maintained/calculatable. + skipable_pubkeys.push(None); + } + } + } + + let mut signers: Vec = Vec::new(); + let mut non_signers: Vec = Vec::new(); + + let mut add_to_signers = |meta: &solana_instruction::AccountMeta| { + if skipable_pubkeys.contains(&Some(meta.pubkey)) { + return; + } + + assert!(meta.is_signer, "AccountMeta must be a signer"); + let Some(found) = + signers.iter_mut().find(|m| m.pubkey == meta.pubkey) + else { + signers.push(meta.clone()); + return; + }; + + found.is_writable |= meta.is_writable; + }; + + let mut add_to_non_signers = + |meta: &solana_instruction::AccountMeta| { + if skipable_pubkeys.contains(&Some(meta.pubkey)) { + return; + } + + assert!(!meta.is_signer, "AccountMeta must not be a signer"); + let Some(found) = + non_signers.iter_mut().find(|m| m.pubkey == meta.pubkey) + else { + non_signers.push(meta.clone()); + return; + }; + + found.is_writable |= meta.is_writable; + }; + + for meta in self + .iter() + .flat_map(|ix| ix.accounts.iter()) + .filter(|meta| meta.is_signer) + { + add_to_signers(meta); + } + + for ix in self.iter() { + add_to_non_signers(&solana_instruction::AccountMeta::new_readonly( + ix.program_id, + false, + )); + for meta in ix.accounts.iter().filter(|meta| !meta.is_signer) { + let Some(found) = + signers.iter_mut().find(|m| m.pubkey == meta.pubkey) + else { + add_to_non_signers(meta); + continue; + }; + + found.is_writable |= meta.is_writable; + } + } + + if signers.len() + non_signers.len() + > crate::compact::MAX_PUBKEYS as usize + { + panic!( + "delegate_with_actions supports at most {} unique pubkeys", + crate::compact::MAX_PUBKEYS + ); + } + + let old_signers_len = insertable.signers.len(); + let old_non_signers_len = insertable.non_signers.len(); + let old_total = old_signers_len + old_non_signers_len; + + let index_of = |pk: &solana_address::Address| -> u8 { + // The final list will be this as per PostDelegationActions: + // + // [insertable.signers..., new.signers..., insertable.non_signers..., new.non_signers...] + // + // However, the final list will invalidate the indices (of non-signers) referred to + // by insertable.instructions, though indices of signers will continue to be correct. + // + // To deal with that, we need to compute the indices of newly added pubkeys + // differently, accordingly to this imagined list: + // + // [insertable.signers..., insertable.non_signers..., new.signers..., new.non_signers...] + // + // That means, if a key is found in skipable_pubkeys, its index will be returned as it + // is. Else, we'll add `old_total` to the index computed for the following list: + // + // [new.signers..., new.non_signers...] + // + if let Some(index) = skipable_pubkeys + .iter() + .position(|pubkey| pubkey == &Some(*pk)) + { + return index as u8; + } + + if let Some(index) = signers.iter().position(|s| &s.pubkey == pk) { + return (old_total + index) as u8; + } + (old_total + + signers.len() + + non_signers.iter().position(|ns| &ns.pubkey == pk).unwrap()) + as u8 + }; + + let mut compact_instructions: Vec = self + .into_iter() + .map(|ix| MaybeEncryptedInstruction { + program_id: index_of(&ix.program_id), + + accounts: ix + .accounts + .into_iter() + .map(|meta| { + let index = index_of(&meta.pubkey); + crate::compact::AccountMeta::try_new( + index, + meta.is_signer, + meta.is_writable, + ) + .expect("compact account index must fit in 6 bits") + .cleartext() + }) + .collect(), + + data: ix.data.cleartext(), + }) + .collect(); + + // Merge all parts now + let mut rv = insertable; + rv.inserted_signers = old_signers_len as u8; + rv.inserted_non_signers = old_non_signers_len as u8; + + rv.signers.extend_from_slice( + &signers + .iter() + .map(|s| s.pubkey.to_bytes()) + .collect::>(), + ); + rv.non_signers.extend_from_slice( + &non_signers + .iter() + .map(|ns| MaybeEncryptedPubkey::ClearText(ns.pubkey.to_bytes())) + .collect::>(), + ); + + if insert_before_index <= compact_instructions.len() { + compact_instructions.splice( + insert_before_index..insert_before_index, + rv.instructions, + ); + } else { + compact_instructions.extend_from_slice(&rv.instructions); + } + + rv.instructions = compact_instructions; + + rv + } +} + +#[cfg(test)] +mod tests { + use solana_instruction::{AccountMeta, Instruction}; + use solana_pubkey::Pubkey; + + use super::*; + use crate::args::MaybeEncryptedAccountMeta; + + fn pk(byte: u8) -> Pubkey { + Pubkey::new_from_array([byte; 32]) + } + + fn assert_cleartext_meta( + meta: &MaybeEncryptedAccountMeta, + expected_index: u8, + expected_signer: bool, + ) { + let MaybeEncryptedAccountMeta::ClearText(meta) = meta else { + panic!("expected cleartext account meta"); + }; + assert_eq!(meta.key(), expected_index); + assert_eq!(meta.is_signer(), expected_signer); + } + + #[test] + fn test_cleartext_with_insertable_indices() { + let s1 = pk(1); + let s2 = pk(2); + let n1 = pk(3); + let n2 = pk(4); + let n3 = pk(5); + + let insertable = PostDelegationActions { + inserted_signers: 0, + inserted_non_signers: 0, + signers: vec![s1.to_bytes(), s2.to_bytes()], + non_signers: vec![ + MaybeEncryptedPubkey::ClearText(n1.to_bytes()), + MaybeEncryptedPubkey::ClearText(n2.to_bytes()), + MaybeEncryptedPubkey::Encrypted(EncryptedBuffer::new( + n3.to_bytes().into(), + )), + ], + instructions: vec![MaybeEncryptedInstruction { + program_id: 0, + accounts: vec![], + data: MaybeEncryptedIxData { + prefix: vec![], + suffix: EncryptedBuffer::new(vec![]), + }, + }], + }; + + let ns1 = pk(6); + let nn1 = pk(7); + let program_id = pk(8); + + let ix = Instruction { + program_id, + accounts: vec![ + AccountMeta::new_readonly(s1, true), // reuse old key + AccountMeta::new_readonly(ns1, true), + AccountMeta::new_readonly(nn1, false), + AccountMeta::new_readonly(n3, false), // reuse old key but encrypted + ], + data: vec![1, 2, 3], + }; + + let actions = vec![ix].cleartext_with_insertable(insertable, 1); + + assert_eq!(actions.inserted_signers, 2); + assert_eq!(actions.inserted_non_signers, 3); // even though 1 is encrypted + + assert_eq!(actions.signers.len(), 3); + assert_eq!(actions.non_signers.len(), 5 + 1); // n3 is inserted again + + assert_eq!( + actions.signers, + vec![s1.to_bytes(), s2.to_bytes(), ns1.to_bytes()] + ); + assert_eq!( + actions.non_signers, + vec![ + MaybeEncryptedPubkey::ClearText(n1.to_bytes()), + MaybeEncryptedPubkey::ClearText(n2.to_bytes()), + MaybeEncryptedPubkey::Encrypted(EncryptedBuffer::new( + n3.to_bytes().into(), + )), + MaybeEncryptedPubkey::ClearText(program_id.to_bytes()), + MaybeEncryptedPubkey::ClearText(nn1.to_bytes()), + MaybeEncryptedPubkey::ClearText(n3.to_bytes()), + ] + ); + + assert_eq!(actions.instructions.len(), 2); + let new_ix = &actions.instructions[0]; + assert_eq!(new_ix.program_id, 6); + assert_eq!(new_ix.accounts.len(), 4); + + assert_cleartext_meta(&new_ix.accounts[0], 0, true); + assert_cleartext_meta(&new_ix.accounts[1], 5, true); + assert_cleartext_meta(&new_ix.accounts[2], 7, false); + assert_cleartext_meta(&new_ix.accounts[3], 8, false); + } +} diff --git a/src/consts.rs b/src/consts.rs index db4597f1..089ae34d 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -35,11 +35,13 @@ pub const DEFAULT_VALIDATOR_IDENTITY: Pubkey = pub const BROADCAST_IDENTITY: Pubkey = pubkey!("Broadcast1111111111111111111111111111111111"); +#[cfg(any(feature = "processor", feature = "pinocchio-rt"))] pub const BPF_LOADER_UPGRADEABLE_ID: Address = Address::new_from_array(const_crypto::bs58::decode_pubkey( "BPFLoaderUpgradeab1e11111111111111111111111", )); +#[cfg(any(feature = "processor", feature = "pinocchio-rt"))] pub const DELEGATION_PROGRAM_DATA_ID: Address = Address::new_from_array( const_crypto::ed25519::derive_program_address( &[crate::fast::ID.as_array()], diff --git a/src/diff/algorithm.rs b/src/diff/algorithm.rs index 7f51e0e7..ed34f44e 100644 --- a/src/diff/algorithm.rs +++ b/src/diff/algorithm.rs @@ -558,7 +558,7 @@ mod tests { // TODO (snawaz): unwritten == &mut [], is because currently the expanded bytes are part of the diff. // Once compute_diff is optimized further, written must be &mut [0; 20]. - assert_eq!(unwritten, &mut []); + assert_eq!(unwritten, &mut [] as &mut [u8]); destination }; diff --git a/src/discriminator.rs b/src/discriminator.rs index 1af1d58d..c18410de 100644 --- a/src/discriminator.rs +++ b/src/discriminator.rs @@ -53,6 +53,9 @@ pub enum DlpDiscriminator { /// See [crate::processor::process_commit_finalize_from_buffer] for docs. CommitFinalizeFromBuffer = 22, + + /// See [crate::processor::process_delegate_with_actions] for docs. + DelegateWithActions = 23, } impl DlpDiscriminator { diff --git a/src/error.rs b/src/error.rs index ad12d704..edbed290 100644 --- a/src/error.rs +++ b/src/error.rs @@ -159,14 +159,14 @@ impl From for ProgramError { } } -#[cfg(not(feature = "sdk"))] +#[cfg(feature = "pinocchio-rt")] impl From for pinocchio::error::ProgramError { fn from(e: DlpError) -> Self { pinocchio::error::ProgramError::Custom(e as u32) } } -#[cfg(not(feature = "sdk"))] +#[cfg(feature = "pinocchio-rt")] impl pinocchio::error::ToStr for DlpError { fn to_str(&self) -> &'static str { self.into() diff --git a/src/lib.rs b/src/lib.rs index 3e94e488..5720b7f4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,20 +1,20 @@ #![allow(unexpected_cfgs)] // Exactly one of `sdk` or `program` must be enabled -#[cfg(all(feature = "sdk", feature = "program"))] -compile_error!( - "Features `sdk` and `program` are mutually exclusive. Enable exactly one." -); - -#[cfg(all(not(feature = "sdk"), not(feature = "program")))] -compile_error!( - "Enable either `program` (default) or `sdk`. Building with neither is not supported." -); +//#[cfg(all(feature = "sdk", feature = "program"))] +//compile_error!( +// "Features `sdk` and `program` are mutually exclusive. Enable exactly one." +//); +// +//#[cfg(all(not(feature = "sdk"), not(feature = "program")))] +//compile_error!( +// "Enable either `program` (default) or `sdk`. Building with neither is not supported." +//); use solana_program::declare_id; #[cfg(feature = "logging")] use solana_program::msg; -#[cfg(not(feature = "sdk"))] +#[cfg(feature = "processor")] use { crate::discriminator::DlpDiscriminator, solana_program::{ @@ -24,39 +24,40 @@ use { }; pub mod args; +pub mod compact; pub mod consts; -mod discriminator; +pub mod discriminator; pub mod error; -pub mod instruction_builder; pub mod pda; pub mod pod_view; +pub mod requires; pub mod state; mod account_size_class; pub use account_size_class::*; -#[cfg(not(feature = "sdk"))] +#[cfg(feature = "diff")] mod diff; -#[cfg(not(feature = "sdk"))] +#[cfg(feature = "processor")] mod processor; -#[cfg(not(feature = "sdk"))] +#[cfg(feature = "diff")] pub use diff::*; // re-export -#[cfg(not(feature = "sdk"))] +#[cfg(feature = "diff")] pub use rkyv; #[cfg(feature = "log-cost")] mod cu; -#[cfg(not(feature = "no-entrypoint"))] +#[cfg(all(feature = "entrypoint", not(feature = "no-entrypoint")))] mod entrypoint; declare_id!("DELeGGvXpWV2fqJUhqcF5ZSYMS4JTLjteaAMARRSaeSh"); -#[cfg(not(feature = "sdk"))] +#[cfg(any(feature = "processor", feature = "pinocchio-rt"))] pub mod fast { pinocchio::address::declare_id!( "DELeGGvXpWV2fqJUhqcF5ZSYMS4JTLjteaAMARRSaeSh" @@ -73,7 +74,7 @@ solana_security_txt::security_txt! { source_code: "https://github.com/magicblock-labs/delegation-program" } -#[cfg(not(feature = "sdk"))] +#[cfg(feature = "processor")] pub fn fast_process_instruction( program_id: &pinocchio::Address, accounts: &[pinocchio::AccountView], @@ -110,6 +111,11 @@ pub fn fast_process_instruction( program_id, accounts, data, )) } + DlpDiscriminator::DelegateWithActions => { + Some(processor::fast::process_delegate_with_actions( + program_id, accounts, data, + )) + } DlpDiscriminator::CommitState => Some( processor::fast::process_commit_state(program_id, accounts, data), ), @@ -151,7 +157,7 @@ pub fn fast_process_instruction( } } -#[cfg(not(feature = "sdk"))] +#[cfg(feature = "processor")] pub fn slow_process_instruction( program_id: &Pubkey, accounts: &[AccountInfo], diff --git a/src/processor/delegate_ephemeral_balance.rs b/src/processor/delegate_ephemeral_balance.rs index c4023918..60993834 100644 --- a/src/processor/delegate_ephemeral_balance.rs +++ b/src/processor/delegate_ephemeral_balance.rs @@ -1,13 +1,23 @@ use borsh::BorshDeserialize; use solana_program::{ - account_info::AccountInfo, entrypoint::ProgramResult, - program::invoke_signed, program_error::ProgramError, pubkey::Pubkey, + account_info::AccountInfo, + entrypoint::ProgramResult, + instruction::{AccountMeta, Instruction}, + program::invoke_signed, + program_error::ProgramError, + pubkey::Pubkey, system_instruction, system_program, }; use crate::{ args::DelegateEphemeralBalanceArgs, + discriminator::DlpDiscriminator, ephemeral_balance_seeds_from_payer, + pda::{ + delegate_buffer_pda_from_delegated_account_and_owner_program, + delegation_metadata_pda_from_delegated_account, + delegation_record_pda_from_delegated_account, + }, processor::utils::loaders::{load_program, load_signer}, }; @@ -78,13 +88,35 @@ pub fn process_delegate_ephemeral_balance( &[&ephemeral_balance_signer_seeds], )?; - // Create the delegation ix - let ix = crate::instruction_builder::delegate( - *payer.key, - *ephemeral_balance_account.key, - Some(system_program::id()), - args.delegate_args, + let delegate_buffer_pda = + delegate_buffer_pda_from_delegated_account_and_owner_program( + ephemeral_balance_account.key, + &system_program::id(), + ); + let delegation_record_pda = delegation_record_pda_from_delegated_account( + ephemeral_balance_account.key, ); + let delegation_metadata_pda = + delegation_metadata_pda_from_delegated_account( + ephemeral_balance_account.key, + ); + let mut data = DlpDiscriminator::Delegate.to_vec(); + data.extend_from_slice(&borsh::to_vec(&args.delegate_args).unwrap()); + + // Create the delegation ix + let ix = Instruction { + program_id: crate::id(), + accounts: vec![ + AccountMeta::new(*payer.key, true), + AccountMeta::new(*ephemeral_balance_account.key, true), + AccountMeta::new_readonly(system_program::id(), false), + AccountMeta::new(delegate_buffer_pda, false), + AccountMeta::new(delegation_record_pda, false), + AccountMeta::new(delegation_metadata_pda, false), + AccountMeta::new_readonly(system_program::id(), false), + ], + data, + }; // Invoke signed delegation instruction invoke_signed( diff --git a/src/processor/fast/commit_state.rs b/src/processor/fast/commit_state.rs index 8bc90623..66c6c850 100644 --- a/src/processor/fast/commit_state.rs +++ b/src/processor/fast/commit_state.rs @@ -11,15 +11,13 @@ use crate::{ args::CommitStateArgs, error::DlpError, merge_diff_copy, pda, - processor::fast::utils::{ - pda::create_pda, - requires::{ - require_initialized_delegation_metadata, - require_initialized_delegation_record, - require_initialized_validator_fees_vault, require_owned_pda, - require_program_config, require_signer, require_uninitialized_pda, - CommitRecordCtx, CommitStateAccountCtx, - }, + processor::fast::utils::pda::create_pda, + requires::{ + require_initialized_delegation_metadata, + require_initialized_delegation_record, + require_initialized_validator_fees_vault, require_owned_pda, + require_program_config, require_signer, require_uninitialized_pda, + CommitRecordCtx, CommitStateAccountCtx, }, state::{ CommitRecord, DelegationMetadata, DelegationRecord, ProgramConfig, diff --git a/src/processor/fast/delegate.rs b/src/processor/fast/delegate.rs index 6d8b379f..54b525cb 100644 --- a/src/processor/fast/delegate.rs +++ b/src/processor/fast/delegate.rs @@ -15,19 +15,13 @@ use crate::{ error::DlpError, pda, processor::{ - fast::{ - to_pinocchio_program_error, - utils::{ - pda::create_pda, - requires::{ - require_owned_pda, require_pda, require_signer, - require_uninitialized_pda, DelegationMetadataCtx, - DelegationRecordCtx, - }, - }, - }, + fast::{to_pinocchio_program_error, utils::pda::create_pda}, utils::curve::is_on_curve_fast, }, + requires::{ + require_owned_pda, require_pda, require_signer, + require_uninitialized_pda, DelegationMetadataCtx, DelegationRecordCtx, + }, state::{DelegationMetadata, DelegationRecord}, }; diff --git a/src/processor/fast/delegate_with_actions.rs b/src/processor/fast/delegate_with_actions.rs new file mode 100644 index 00000000..2c9b29fb --- /dev/null +++ b/src/processor/fast/delegate_with_actions.rs @@ -0,0 +1,305 @@ +use borsh::BorshDeserialize; +use pinocchio::{ + address::address_eq, + cpi::{Seed, Signer}, + error::ProgramError, + sysvars::{clock::Clock, Sysvar}, + AccountView, Address, ProgramResult, +}; +use pinocchio_log::log; +use pinocchio_system::instructions as system; + +use crate::{ + args::DelegateWithActionsArgs, + compact, + consts::{DEFAULT_VALIDATOR_IDENTITY, RENT_EXCEPTION_ZERO_BYTES_LAMPORTS}, + error::DlpError, + pda, + processor::{ + fast::{to_pinocchio_program_error, utils::pda::create_pda}, + utils::curve::is_on_curve_fast, + }, + require, require_n_accounts_with_optionals, + requires::{ + require_owned_pda, require_pda, require_signer, + require_uninitialized_pda, DelegationMetadataCtx, DelegationRecordCtx, + }, + state::{DelegationMetadata, DelegationRecord}, +}; + +/// Delegates an account and stores an actions payload. +pub fn process_delegate_with_actions( + _program_id: &Address, + accounts: &[AccountView], + data: &[u8], +) -> ProgramResult { + let ( + [ + payer, // force multi-line + delegated_account, + owner_program, + delegate_buffer_account, + delegation_record_account, + delegation_metadata_account, + _system_program, + ], + remaining_accounts, + ) = require_n_accounts_with_optionals!(accounts, 7); + + require_owned_pda( + delegated_account, + &crate::fast::ID, + "delegated account", + )?; + + // Check that payer and delegated_account are signers, this ensures the instruction is being called from CPI + require_signer(payer, "payer")?; + require_signer(delegated_account, "delegated account")?; + + // Check that the buffer PDA is initialized and derived correctly from the PDA + require_pda( + delegate_buffer_account, + &[ + pda::DELEGATE_BUFFER_TAG, + delegated_account.address().as_ref(), + ], + owner_program.address(), + true, + "delegate buffer", + )?; + + // Check that the delegation record PDA is uninitialized + let delegation_record_bump = require_uninitialized_pda( + delegation_record_account, + &[ + pda::DELEGATION_RECORD_TAG, + delegated_account.address().as_ref(), + ], + &crate::fast::ID, + true, + DelegationRecordCtx, + )?; + + // Check that the delegation metadata PDA is uninitialized + let delegation_metadata_bump = require_uninitialized_pda( + delegation_metadata_account, + &[ + pda::DELEGATION_METADATA_TAG, + delegated_account.address().as_ref(), + ], + &crate::fast::ID, + true, + DelegationMetadataCtx, + )?; + + let args: DelegateWithActionsArgs = + DelegateWithActionsArgs::try_from_slice(data) + .map_err(|_| ProgramError::InvalidInstructionData)?; + + // Validate instruction payload shape up-front. This confirms delegate args + // and actions envelope format, while encrypted bytes remain opaque. + { + if (args.actions.signers.len() + args.actions.non_signers.len()) + > compact::MAX_PUBKEYS as usize + { + return Err(ProgramError::InvalidInstructionData); + } + + let signers_count = args.actions.signers.len() as u8; + let keys_count = signers_count + args.actions.non_signers.len() as u8; + + for ix in args.actions.instructions.iter() { + require!( + ix.program_id < keys_count, + ProgramError::InvalidInstructionData + ); + for account in &ix.accounts { + let crate::args::MaybeEncryptedAccountMeta::ClearText(meta) = + account + else { + continue; + }; + require!( + (meta.is_signer() && meta.key() < signers_count) + || meta.key() < keys_count, + ProgramError::InvalidInstructionData + ); + } + } + + // Enforce required signers from the pubkey-table prefix. + for signer in &args.actions.signers { + let account = remaining_accounts + .iter() + .find(|account| &account.address().to_bytes() == signer) + .ok_or(ProgramError::NotEnoughAccountKeys)?; + if !account.is_signer() { + return Err(ProgramError::MissingRequiredSignature); + } + } + } + + if let Some(validator) = args.delegate.validator { + if validator.to_bytes() == pinocchio_system::ID.to_bytes() { + return Err(DlpError::DelegationToSystemProgramNotAllowed.into()); + } + } + + // Validate seeds if the delegate account is not on curve, i.e. is a PDA + // If the owner is the system program, we check if the account is derived from the delegation program, + // allowing delegation of escrow accounts + if !is_on_curve_fast(delegated_account.address()) { + let program_id = + if address_eq(owner_program.address(), &pinocchio_system::ID) { + &crate::fast::ID + } else { + owner_program.address() + }; + let seeds_to_validate: &[&[u8]] = match args.delegate.seeds.len() { + 1 => &[&args.delegate.seeds[0]], + 2 => &[&args.delegate.seeds[0], &args.delegate.seeds[1]], + 3 => &[ + &args.delegate.seeds[0], + &args.delegate.seeds[1], + &args.delegate.seeds[2], + ], + 4 => &[ + &args.delegate.seeds[0], + &args.delegate.seeds[1], + &args.delegate.seeds[2], + &args.delegate.seeds[3], + ], + 5 => &[ + &args.delegate.seeds[0], + &args.delegate.seeds[1], + &args.delegate.seeds[2], + &args.delegate.seeds[3], + &args.delegate.seeds[4], + ], + 6 => &[ + &args.delegate.seeds[0], + &args.delegate.seeds[1], + &args.delegate.seeds[2], + &args.delegate.seeds[3], + &args.delegate.seeds[4], + &args.delegate.seeds[5], + ], + 7 => &[ + &args.delegate.seeds[0], + &args.delegate.seeds[1], + &args.delegate.seeds[2], + &args.delegate.seeds[3], + &args.delegate.seeds[4], + &args.delegate.seeds[5], + &args.delegate.seeds[6], + ], + 8 => &[ + &args.delegate.seeds[0], + &args.delegate.seeds[1], + &args.delegate.seeds[2], + &args.delegate.seeds[3], + &args.delegate.seeds[4], + &args.delegate.seeds[5], + &args.delegate.seeds[6], + &args.delegate.seeds[7], + ], + _ => return Err(DlpError::TooManySeeds.into()), + }; + let derived_pda = + Address::find_program_address(seeds_to_validate, program_id).0; + + if !address_eq(&derived_pda, delegated_account.address()) { + log!("Expected delegated PDA to be: "); + derived_pda.log(); + log!("but got: "); + delegated_account.address().log(); + return Err(ProgramError::InvalidSeeds); + } + } + + let action_data = borsh::to_vec(&args.actions) + .map_err(|_| ProgramError::InvalidInstructionData)?; + + create_pda( + delegation_record_account, + &crate::fast::ID, + DelegationRecord::size_with_discriminator() + action_data.len(), + &[Signer::from(&[ + Seed::from(pda::DELEGATION_RECORD_TAG), + Seed::from(delegated_account.address().as_ref()), + Seed::from(&[delegation_record_bump]), + ])], + payer, + )?; + + // Initialize the delegation record + let delegation_record = DelegationRecord { + owner: owner_program.address().to_bytes().into(), + authority: args + .delegate + .validator + .unwrap_or(DEFAULT_VALIDATOR_IDENTITY), + commit_frequency_ms: args.delegate.commit_frequency_ms as u64, + delegation_slot: Clock::get()?.slot, + lamports: delegated_account.lamports(), + }; + + let mut delegation_record_data = + delegation_record_account.try_borrow_mut()?; + let record_size = DelegationRecord::size_with_discriminator(); + if delegation_record_data.len() != record_size + action_data.len() { + return Err(DlpError::InvalidDataLength.into()); + } + let (record_bytes, action_bytes) = + delegation_record_data.split_at_mut(record_size); + delegation_record + .to_bytes_with_discriminator(record_bytes) + .map_err(to_pinocchio_program_error)?; + action_bytes.copy_from_slice(&action_data); + + let delegation_metadata = DelegationMetadata { + seeds: args.delegate.seeds, + last_update_nonce: 0, + is_undelegatable: false, + rent_payer: payer.address().to_bytes().into(), + }; + + // Initialize the delegation metadata PDA + create_pda( + delegation_metadata_account, + &crate::fast::ID, + delegation_metadata.serialized_size(), + &[Signer::from(&[ + Seed::from(pda::DELEGATION_METADATA_TAG), + Seed::from(delegated_account.address().as_ref()), + Seed::from(&[delegation_metadata_bump]), + ])], + payer, + )?; + + // Copy the seeds to the delegated metadata PDA + let mut delegation_metadata_data = + delegation_metadata_account.try_borrow_mut()?; + delegation_metadata + .to_bytes_with_discriminator(&mut delegation_metadata_data.as_mut()) + .map_err(to_pinocchio_program_error)?; + + // Copy the data from the buffer into the original account + if !delegate_buffer_account.is_data_empty() { + let mut delegated_data = delegated_account.try_borrow_mut()?; + let delegate_buffer_data = delegate_buffer_account.try_borrow()?; + (*delegated_data).copy_from_slice(&delegate_buffer_data); + } + + // Make the account rent exempt if it's not + if delegated_account.lamports() == 0 && delegated_account.data_len() == 0 { + system::Transfer { + from: payer, + to: delegated_account, + lamports: RENT_EXCEPTION_ZERO_BYTES_LAMPORTS, + } + .invoke()?; + } + + Ok(()) +} diff --git a/src/processor/fast/finalize.rs b/src/processor/fast/finalize.rs index 1da91338..ebfc1136 100644 --- a/src/processor/fast/finalize.rs +++ b/src/processor/fast/finalize.rs @@ -8,16 +8,14 @@ use pinocchio_log::log; use super::to_pinocchio_program_error; use crate::{ error::DlpError, - processor::fast::utils::{ - pda::close_pda, - requires::{ - is_uninitialized_account, require_initialized_commit_record, - require_initialized_commit_state, - require_initialized_delegation_metadata, - require_initialized_delegation_record, - require_initialized_validator_fees_vault, require_owned_pda, - require_signer, - }, + processor::fast::utils::pda::close_pda, + requires::{ + is_uninitialized_account, require_initialized_commit_record, + require_initialized_commit_state, + require_initialized_delegation_metadata, + require_initialized_delegation_record, + require_initialized_validator_fees_vault, require_owned_pda, + require_signer, }, state::{CommitRecord, DelegationMetadata, DelegationRecord}, }; diff --git a/src/processor/fast/mod.rs b/src/processor/fast/mod.rs index 8413fdcb..2e975201 100644 --- a/src/processor/fast/mod.rs +++ b/src/processor/fast/mod.rs @@ -5,6 +5,7 @@ mod commit_finalize_from_buffer; mod commit_state; mod commit_state_from_buffer; mod delegate; +mod delegate_with_actions; mod finalize; mod undelegate; mod undelegate_confined_account; @@ -19,6 +20,7 @@ pub use commit_finalize_from_buffer::*; pub use commit_state::*; pub use commit_state_from_buffer::*; pub use delegate::*; +pub use delegate_with_actions::*; pub use finalize::*; pub use undelegate::*; pub use undelegate_confined_account::*; diff --git a/src/processor/fast/undelegate.rs b/src/processor/fast/undelegate.rs index 0750f12d..a1fa4b1c 100644 --- a/src/processor/fast/undelegate.rs +++ b/src/processor/fast/undelegate.rs @@ -9,16 +9,7 @@ use pinocchio::{ use pinocchio_log::log; use pinocchio_system::instructions as system; -use super::{ - to_pinocchio_program_error, - utils::requires::{ - require_initialized_delegation_metadata, - require_initialized_delegation_record, - require_initialized_protocol_fees_vault, - require_initialized_validator_fees_vault, require_owned_pda, - require_signer, - }, -}; +use super::to_pinocchio_program_error; #[cfg(feature = "log-cost")] use crate::compute; use crate::{ @@ -28,12 +19,14 @@ use crate::{ }, error::DlpError, pda, - processor::fast::utils::{ - pda::{close_pda, close_pda_with_fees, create_pda}, - requires::{ - require_uninitialized_pda, CommitRecordCtx, CommitStateAccountCtx, - UndelegateBufferCtx, - }, + processor::fast::utils::pda::{close_pda, close_pda_with_fees, create_pda}, + requires::{ + require_initialized_delegation_metadata, + require_initialized_delegation_record, + require_initialized_protocol_fees_vault, + require_initialized_validator_fees_vault, require_owned_pda, + require_signer, require_uninitialized_pda, CommitRecordCtx, + CommitStateAccountCtx, UndelegateBufferCtx, }, state::{DelegationMetadata, DelegationRecord}, }; diff --git a/src/processor/fast/undelegate_confined_account.rs b/src/processor/fast/undelegate_confined_account.rs index e5cea29b..52879fe9 100644 --- a/src/processor/fast/undelegate_confined_account.rs +++ b/src/processor/fast/undelegate_confined_account.rs @@ -7,15 +7,13 @@ use super::{process_undelegation_with_cpi, to_pinocchio_program_error}; use crate::{ error::DlpError, pda, - processor::fast::utils::{ - pda::{close_pda, create_pda}, - requires::{ - require_authorization, require_initialized_delegation_metadata, - require_initialized_delegation_record, require_owned_pda, - require_signer, require_uninitialized_pda, UndelegateBufferCtx, - }, - }, + processor::fast::utils::pda::{close_pda, create_pda}, require_eq_keys, + requires::{ + require_authorization, require_initialized_delegation_metadata, + require_initialized_delegation_record, require_owned_pda, + require_signer, require_uninitialized_pda, UndelegateBufferCtx, + }, state::{DelegationMetadata, DelegationRecord}, }; diff --git a/src/processor/fast/utils/mod.rs b/src/processor/fast/utils/mod.rs index 02cf2aa7..f52daa8e 100644 --- a/src/processor/fast/utils/mod.rs +++ b/src/processor/fast/utils/mod.rs @@ -1,2 +1 @@ pub(crate) mod pda; -pub(crate) mod requires; diff --git a/src/processor/fast/utils/requires.rs b/src/requires.rs similarity index 91% rename from src/processor/fast/utils/requires.rs rename to src/requires.rs index 3bee15df..97ef70d6 100644 --- a/src/processor/fast/utils/requires.rs +++ b/src/requires.rs @@ -169,6 +169,34 @@ macro_rules! require_n_accounts { }}; } +#[macro_export] +macro_rules! require_n_accounts_with_optionals { + ( $accounts:expr, $n:literal) => {{ + match $accounts.len().cmp(&$n) { + core::cmp::Ordering::Less => { + pinocchio_log::log!( + "Need {} accounts, but got less ({}) accounts", + $n, + $accounts.len() + ); + return Err( + pinocchio::error::ProgramError::NotEnoughAccountKeys, + ); + } + _ => { + let (exact, optionals) = $accounts.split_at($n); + + ( + TryInto::<&[_; $n]>::try_into(exact).map_err(|_| { + $crate::error::DlpError::InfallibleError + })?, + optionals, + ) + } + } + }}; +} + #[macro_export] macro_rules! require_some { ($option:expr, $error:expr) => {{ @@ -330,7 +358,8 @@ macro_rules! require_pda { /// Errors if: /// - Account is not owned by expected program. #[inline(always)] -pub fn require_owned_pda( +#[cfg(feature = "processor")] +pub(crate) fn require_owned_pda( info: &AccountView, owner: &Address, label: &str, @@ -348,7 +377,8 @@ pub fn require_owned_pda( /// Errors if: /// - Account is not a signer. #[inline(always)] -pub fn require_signer( +#[cfg(feature = "processor")] +pub(crate) fn require_signer( info: &AccountView, label: &str, ) -> Result<(), ProgramError> { @@ -364,7 +394,8 @@ pub fn require_signer( /// Errors if: /// - Address does not match PDA derived from provided seeds. #[inline(always)] -pub fn require_pda( +#[cfg(feature = "processor")] +pub(crate) fn require_pda( info: &AccountView, seeds: &[&[u8]], program_id: &Address, @@ -401,7 +432,8 @@ pub fn is_uninitialized_account(info: &AccountView) -> bool { /// - Data is not empty. /// - Account is not writable. #[inline(always)] -pub fn require_uninitialized_account( +#[cfg(feature = "processor")] +pub(crate) fn require_uninitialized_account( info: &AccountView, is_writable: bool, ctx: impl RequireUninitializedAccountCtx, @@ -441,7 +473,8 @@ pub fn require_uninitialized_account( /// - Address does not match PDA derived from provided seeds. /// - Cannot load as an uninitialized account. #[inline(always)] -pub fn require_uninitialized_pda( +#[cfg(feature = "processor")] +pub(crate) fn require_uninitialized_pda( info: &AccountView, seeds: &[&[u8]], program_id: &Address, @@ -464,7 +497,8 @@ pub fn require_uninitialized_pda( /// - Address does not match PDA derived from provided seeds. /// - Owner is not the expected program. /// - Account is not writable if set to writable. -pub fn require_initialized_pda( +#[cfg(feature = "processor")] +pub(crate) fn require_initialized_pda( info: &AccountView, seeds: &[&[u8]], program_id: &Address, @@ -494,7 +528,8 @@ pub fn require_initialized_pda( /// - Account is not executable. #[inline(always)] #[allow(dead_code)] -pub fn require_program( +#[cfg(feature = "processor")] +pub(crate) fn require_program( info: &AccountView, key: &Address, label: &str, @@ -516,7 +551,8 @@ pub fn require_program( /// Load fee vault PDA /// - Protocol fees vault PDA -pub fn require_initialized_protocol_fees_vault( +#[cfg(feature = "processor")] +pub(crate) fn require_initialized_protocol_fees_vault( fees_vault: &AccountView, is_writable: bool, ) -> Result<(), ProgramError> { @@ -533,7 +569,8 @@ pub fn require_initialized_protocol_fees_vault( /// Load validator fee vault PDA /// - Validator fees vault PDA must be derived from the validator pubkey /// - Validator fees vault PDA must be initialized with the expected seeds and owner -pub fn require_initialized_validator_fees_vault( +#[cfg(feature = "processor")] +pub(crate) fn require_initialized_validator_fees_vault( validator: &AccountView, validator_fees_vault: &AccountView, is_writable: bool, @@ -562,7 +599,8 @@ pub fn require_initialized_validator_fees_vault( /// Load program config PDA /// - Program config PDA must be initialized with the expected seeds and owner, or not exists -pub fn require_program_config( +#[cfg(feature = "processor")] +pub(crate) fn require_program_config( program_config: &AccountView, program: &Address, is_writable: bool, @@ -590,7 +628,8 @@ pub fn require_program_config( /// Load initialized delegation record /// - Delegation record must be derived from the delegated account -pub fn require_initialized_delegation_record( +#[cfg(feature = "processor")] +pub(crate) fn require_initialized_delegation_record( delegated_account: &AccountView, delegation_record: &AccountView, is_writable: bool, @@ -610,7 +649,8 @@ pub fn require_initialized_delegation_record( /// Load initialized delegation metadata /// - Delegation metadata must be derived from the delegated account -pub fn require_initialized_delegation_metadata( +#[cfg(feature = "processor")] +pub(crate) fn require_initialized_delegation_metadata( delegated_account: &AccountView, delegation_metadata: &AccountView, is_writable: bool, @@ -630,7 +670,8 @@ pub fn require_initialized_delegation_metadata( /// Load initialized commit state account /// - Commit state account must be derived from the delegated account pubkey -pub fn require_initialized_commit_state( +#[cfg(feature = "processor")] +pub(crate) fn require_initialized_commit_state( delegated_account: &AccountView, commit_state: &AccountView, is_writable: bool, @@ -647,7 +688,8 @@ pub fn require_initialized_commit_state( /// Load initialized commit state record /// - Commit record account must be derived from the delegated account pubkey -pub fn require_initialized_commit_record( +#[cfg(feature = "processor")] +pub(crate) fn require_initialized_commit_record( delegated_account: &AccountView, commit_record: &AccountView, is_writable: bool, @@ -686,7 +728,7 @@ macro_rules! define_uninitialized_ctx { ) => { pub(crate) struct $name; - impl $crate::processor::fast::utils::requires::RequireUninitializedAccountCtx for $name { + impl $crate::requires::RequireUninitializedAccountCtx for $name { fn label(&self) -> &str { $label } @@ -699,7 +741,9 @@ macro_rules! define_uninitialized_ctx { $owner.into() } - fn account_already_initialized(&self) -> pinocchio::error::ProgramError { + fn account_already_initialized( + &self, + ) -> pinocchio::error::ProgramError { $already_init.into() } @@ -756,7 +800,8 @@ define_uninitialized_ctx!( immutable = DlpError::UndelegateBufferImmutable ); -pub fn require_authorization( +#[cfg(feature = "processor")] +pub(crate) fn require_authorization( program_data: &AccountView, admin: &AccountView, ) -> Result<(), ProgramError> { @@ -771,10 +816,10 @@ pub fn require_authorization( admin.address(), ProgramError::IncorrectAuthority ); - return Ok(()); + Ok(()) } - #[cfg(not(feature = "unit_test_config"))] + #[cfg(all(not(feature = "unit_test_config"), feature = "processor"))] { // Derive and validate program data address require_eq_keys!( diff --git a/src/state/utils/try_from_bytes.rs b/src/state/utils/try_from_bytes.rs index 964f22b0..f3060746 100644 --- a/src/state/utils/try_from_bytes.rs +++ b/src/state/utils/try_from_bytes.rs @@ -5,26 +5,28 @@ macro_rules! impl_try_from_bytes_with_discriminator_zero_copy { pub fn try_from_bytes_with_discriminator( data: &[u8], ) -> Result<&Self, ::solana_program::program_error::ProgramError> { - if data.len() < 8 { + let expected_len = 8 + ::std::mem::size_of::(); + if data.len() < expected_len { return Err($crate::error::DlpError::InvalidDataLength.into()); } if Self::discriminator().to_bytes().ne(&data[..8]) { return Err($crate::error::DlpError::InvalidDiscriminator.into()); } - bytemuck::try_from_bytes::(&data[8..]).or(Err( + bytemuck::try_from_bytes::(&data[8..expected_len]).or(Err( $crate::error::DlpError::InvalidDelegationRecordData.into(), )) } pub fn try_from_bytes_with_discriminator_mut( data: &mut [u8], ) -> Result<&mut Self, ::solana_program::program_error::ProgramError> { - if data.len() < 8 { + let expected_len = 8 + ::std::mem::size_of::(); + if data.len() < expected_len { return Err($crate::error::DlpError::InvalidDataLength.into()); } if Self::discriminator().to_bytes().ne(&data[..8]) { return Err($crate::error::DlpError::InvalidDiscriminator.into()); } - bytemuck::try_from_bytes_mut::(&mut data[8..]).or(Err( + bytemuck::try_from_bytes_mut::(&mut data[8..expected_len]).or(Err( $crate::error::DlpError::InvalidDelegationRecordData.into(), )) } diff --git a/tests/integration/Cargo.lock b/tests/integration/Cargo.lock index 662482fd..f74f2a9f 100644 --- a/tests/integration/Cargo.lock +++ b/tests/integration/Cargo.lock @@ -334,7 +334,7 @@ dependencies = [ "proc-macro-crate 3.3.0", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.117", ] [[package]] @@ -398,7 +398,7 @@ checksum = "7ecc273b49b3205b83d648f0690daa588925572cc5063745bfe547fe7ec8e1a1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.117", ] [[package]] @@ -508,7 +508,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.117", ] [[package]] @@ -810,7 +810,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.117", ] [[package]] @@ -895,18 +895,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.95" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.40" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] @@ -1055,10 +1055,11 @@ checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" [[package]] name = "serde" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ + "serde_core", "serde_derive", ] @@ -1071,15 +1072,24 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + [[package]] name = "serde_derive" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.117", ] [[package]] @@ -1332,7 +1342,7 @@ dependencies = [ "solana-pubkey", "solana-sdk-ids", "solana-system-interface", - "thiserror 2.0.12", + "thiserror 2.0.18", ] [[package]] @@ -1615,7 +1625,7 @@ dependencies = [ "solana-sysvar", "solana-sysvar-id", "solana-vote-interface", - "thiserror 2.0.12", + "thiserror 2.0.18", "wasm-bindgen", ] @@ -1735,7 +1745,7 @@ dependencies = [ "bs58", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.117", ] [[package]] @@ -1746,7 +1756,7 @@ checksum = "baa3120b6cdaa270f39444f5093a90a7b03d296d362878f7a6991d6de3bbe496" dependencies = [ "libsecp256k1", "solana-define-syscall", - "thiserror 2.0.12", + "thiserror 2.0.18", ] [[package]] @@ -1962,9 +1972,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.100" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", @@ -1990,11 +2000,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.12" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl 2.0.12", + "thiserror-impl 2.0.18", ] [[package]] @@ -2005,18 +2015,18 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.117", ] [[package]] name = "thiserror-impl" -version = "2.0.12" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.117", ] [[package]] @@ -2135,7 +2145,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.117", "wasm-bindgen-shared", ] @@ -2157,7 +2167,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.117", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -2280,7 +2290,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.117", ] [[package]] @@ -2291,7 +2301,7 @@ checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.117", ] [[package]] diff --git a/tests/test_call_handler.rs b/tests/test_call_handler.rs index 2f4cfce5..7b58ebaf 100644 --- a/tests/test_call_handler.rs +++ b/tests/test_call_handler.rs @@ -15,7 +15,7 @@ use solana_program::{ hash::Hash, instruction::AccountMeta, native_token::LAMPORTS_PER_SOL, rent::Rent, system_program, }; -use solana_program_test::{read_file, BanksClient, ProgramTest}; +use solana_program_test::{processor, read_file, BanksClient, ProgramTest}; use solana_sdk::{ account::Account, pubkey::Pubkey, @@ -253,7 +253,11 @@ async fn setup_ephemeral_balance( } async fn setup_program_test_env() -> (BanksClient, Keypair, Keypair, Hash) { - let mut program_test = ProgramTest::new("dlp", dlp::ID, None); + let mut program_test = ProgramTest::new( + "dlp", + dlp::ID, + processor!(dlp::slow_process_instruction), + ); program_test.prefer_bpf(true); let payer = Keypair::new(); @@ -327,11 +331,11 @@ async fn test_finalize_call_handler() { let (banks, payer, validator, blockhash) = setup_program_test_env().await; let transfer_destination = Keypair::new(); - let finalize_ix = dlp::instruction_builder::finalize( + let finalize_ix = dlp_api::instruction_builder::finalize( validator.pubkey(), DELEGATED_PDA_ID, ); - let call_handler_ix = dlp::instruction_builder::call_handler( + let call_handler_ix = dlp_api::instruction_builder::call_handler( validator.pubkey(), DELEGATED_PDA_OWNER_ID, // destination program payer.pubkey(), // escrow authority @@ -375,17 +379,17 @@ async fn test_undelegate_call_handler() { let (banks, payer, validator, blockhash) = setup_program_test_env().await; let transfer_destination = Keypair::new(); - let finalize_ix = dlp::instruction_builder::finalize( + let finalize_ix = dlp_api::instruction_builder::finalize( validator.pubkey(), DELEGATED_PDA_ID, ); - let undelegate_ix = dlp::instruction_builder::undelegate( + let undelegate_ix = dlp_api::instruction_builder::undelegate( validator.pubkey(), DELEGATED_PDA_ID, DELEGATED_PDA_OWNER_ID, validator.pubkey(), ); - let call_handler_ix = dlp::instruction_builder::call_handler( + let call_handler_ix = dlp_api::instruction_builder::call_handler( validator.pubkey(), DELEGATED_PDA_OWNER_ID, // destination program payer.pubkey(), // escrow authority @@ -442,11 +446,11 @@ async fn test_finalize_invalid_escrow_call_handler() { // Submit the finalize with handler tx let transfer_destination = Keypair::new(); - let finalize_ix = dlp::instruction_builder::finalize( + let finalize_ix = dlp_api::instruction_builder::finalize( authority.pubkey(), DELEGATED_PDA_ID, ); - let call_handler_ix = dlp::instruction_builder::call_handler( + let call_handler_ix = dlp_api::instruction_builder::call_handler( authority.pubkey(), DELEGATED_PDA_OWNER_ID, // destination program DELEGATED_PDA_ID, @@ -477,11 +481,11 @@ async fn test_undelegate_invalid_escow_call_handler() { // Submit the finalize with handler tx let destination = Keypair::new(); - let finalize_ix = dlp::instruction_builder::finalize( + let finalize_ix = dlp_api::instruction_builder::finalize( authority.pubkey(), DELEGATED_PDA_ID, ); - let finalize_call_handler_ix = dlp::instruction_builder::call_handler( + let finalize_call_handler_ix = dlp_api::instruction_builder::call_handler( authority.pubkey(), DELEGATED_PDA_OWNER_ID, // handler program DELEGATED_PDA_ID, @@ -492,13 +496,13 @@ async fn test_undelegate_invalid_escow_call_handler() { }, ); - let undelegate_ix = dlp::instruction_builder::undelegate( + let undelegate_ix = dlp_api::instruction_builder::undelegate( authority.pubkey(), DELEGATED_PDA_ID, DELEGATED_PDA_OWNER_ID, authority.pubkey(), ); - let undelegate_call_handler_ix = dlp::instruction_builder::call_handler( + let undelegate_call_handler_ix = dlp_api::instruction_builder::call_handler( authority.pubkey(), DELEGATED_PDA_OWNER_ID, // handler program DELEGATED_PDA_ID, diff --git a/tests/test_call_handler_v2.rs b/tests/test_call_handler_v2.rs index 03b5f691..f8edebf8 100644 --- a/tests/test_call_handler_v2.rs +++ b/tests/test_call_handler_v2.rs @@ -327,11 +327,11 @@ async fn test_finalize_call_handler_v2() { let (banks, payer, validator, blockhash) = setup_program_test_env().await; let transfer_destination = Keypair::new(); - let finalize_ix = dlp::instruction_builder::finalize( + let finalize_ix = dlp_api::instruction_builder::finalize( validator.pubkey(), DELEGATED_PDA_ID, ); - let call_handler_v2_ix = dlp::instruction_builder::call_handler_v2( + let call_handler_v2_ix = dlp_api::instruction_builder::call_handler_v2( validator.pubkey(), DELEGATED_PDA_OWNER_ID, // destination program DELEGATED_PDA_OWNER_ID, // source program @@ -376,17 +376,17 @@ async fn test_undelegate_call_handler_v2() { let (banks, payer, validator, blockhash) = setup_program_test_env().await; let transfer_destination = Keypair::new(); - let finalize_ix = dlp::instruction_builder::finalize( + let finalize_ix = dlp_api::instruction_builder::finalize( validator.pubkey(), DELEGATED_PDA_ID, ); - let undelegate_ix = dlp::instruction_builder::undelegate( + let undelegate_ix = dlp_api::instruction_builder::undelegate( validator.pubkey(), DELEGATED_PDA_ID, DELEGATED_PDA_OWNER_ID, validator.pubkey(), ); - let call_handler_v2_ix = dlp::instruction_builder::call_handler_v2( + let call_handler_v2_ix = dlp_api::instruction_builder::call_handler_v2( validator.pubkey(), DELEGATED_PDA_OWNER_ID, // destination program DELEGATED_PDA_OWNER_ID, // source program @@ -442,11 +442,11 @@ async fn test_finalize_invalid_escrow_call_handler_v2() { // Submit the finalize with handler tx let transfer_destination = Keypair::new(); - let finalize_ix = dlp::instruction_builder::finalize( + let finalize_ix = dlp_api::instruction_builder::finalize( authority.pubkey(), DELEGATED_PDA_ID, ); - let call_handler_v2_ix = dlp::instruction_builder::call_handler_v2( + let call_handler_v2_ix = dlp_api::instruction_builder::call_handler_v2( authority.pubkey(), DELEGATED_PDA_OWNER_ID, // destination program DELEGATED_PDA_OWNER_ID, // source program @@ -478,30 +478,31 @@ async fn test_undelegate_invalid_escrow_call_handler_v2() { // Submit the finalize with handler tx let destination = Keypair::new(); - let finalize_ix = dlp::instruction_builder::finalize( + let finalize_ix = dlp_api::instruction_builder::finalize( authority.pubkey(), DELEGATED_PDA_ID, ); - let finalize_call_handler_v2_ix = dlp::instruction_builder::call_handler_v2( - authority.pubkey(), - DELEGATED_PDA_OWNER_ID, // handler program - DELEGATED_PDA_OWNER_ID, // source program - DELEGATED_PDA_ID, - vec![AccountMeta::new(destination.pubkey(), false)], - CallHandlerArgs { - escrow_index: 0, - data: UNDELEGATE_HANDLER_V2_DISCRIMINATOR.to_vec(), - }, - ); + let finalize_call_handler_v2_ix = + dlp_api::instruction_builder::call_handler_v2( + authority.pubkey(), + DELEGATED_PDA_OWNER_ID, // handler program + DELEGATED_PDA_OWNER_ID, // source program + DELEGATED_PDA_ID, + vec![AccountMeta::new(destination.pubkey(), false)], + CallHandlerArgs { + escrow_index: 0, + data: UNDELEGATE_HANDLER_V2_DISCRIMINATOR.to_vec(), + }, + ); - let undelegate_ix = dlp::instruction_builder::undelegate( + let undelegate_ix = dlp_api::instruction_builder::undelegate( authority.pubkey(), DELEGATED_PDA_ID, DELEGATED_PDA_OWNER_ID, authority.pubkey(), ); let undelegate_call_handler_v2_ix = - dlp::instruction_builder::call_handler_v2( + dlp_api::instruction_builder::call_handler_v2( authority.pubkey(), DELEGATED_PDA_OWNER_ID, // handler program DELEGATED_PDA_OWNER_ID, // source program diff --git a/tests/test_cleartext_with_insertable_encrypted.rs b/tests/test_cleartext_with_insertable_encrypted.rs new file mode 100644 index 00000000..eba64410 --- /dev/null +++ b/tests/test_cleartext_with_insertable_encrypted.rs @@ -0,0 +1,85 @@ +use dlp::compact::ClearTextWithInsertable; +use dlp_api::instruction_builder::{ + Encrypt, Encryptable, EncryptableFrom, PostDelegationInstruction, +}; +use solana_instruction::{AccountMeta as IxAccountMeta, Instruction}; +use solana_program::{ + instruction::AccountMeta as ProgramAccountMeta, + pubkey::Pubkey as ProgramPubkey, +}; +use solana_pubkey::Pubkey as IxPubkey; +use solana_sdk::signature::{Keypair, Signer}; + +fn pk_program(byte: u8) -> ProgramPubkey { + ProgramPubkey::new_from_array([byte; 32]) +} + +fn pk_ix(byte: u8) -> IxPubkey { + IxPubkey::new_from_array([byte; 32]) +} + +#[test] +fn test_cleartext_with_insertable_encrypted_actions() { + let validator = Keypair::new(); + let validator_pubkey = + ProgramPubkey::new_from_array(validator.pubkey().to_bytes()); + + // Off-chain: user builds a regular instruction and then chooses which parts + // should be encrypted by converting to PostDelegationInstruction. + let insert_program = pk_program(10); + let s1 = pk_program(1); + let s2 = pk_program(2); + let n1 = pk_program(3); + let n2 = pk_program(4); + let n3 = pk_program(5); + + let insert_ix = PostDelegationInstruction { + // Encrypt program id to keep cleartext keys total at 4 (2 signers + 2 non-signers). + program_id: insert_program.encrypted(), + accounts: vec![ + ProgramAccountMeta::new_readonly(s1, true).cleartext(), + ProgramAccountMeta::new_readonly(s2, true).cleartext(), + ProgramAccountMeta::new_readonly(n1, false).cleartext(), + ProgramAccountMeta::new_readonly(n2, false).cleartext(), + ProgramAccountMeta::new_readonly(n3, false).encrypted(), + ], + data: vec![9, 9, 9].encrypted_from(1), + }; + + let (insertable, _) = vec![insert_ix] + .encrypt(&validator_pubkey) + .expect("post-delegation actions encryption failed"); + + // On-chain: insert the encrypted actions between two cleartext instructions. + let actions = vec![ + Instruction { + program_id: pk_ix(20), + accounts: vec![ + IxAccountMeta::new_readonly(pk_ix(21), true), + IxAccountMeta::new_readonly(pk_ix(22), false), + ], + data: vec![1, 2, 3], + }, + Instruction { + program_id: pk_ix(30), + accounts: vec![ + IxAccountMeta::new_readonly(pk_ix(31), true), + IxAccountMeta::new_readonly(pk_ix(32), false), + ], + data: vec![4, 5, 6], + }, + ] + .cleartext_with_insertable(insertable, 1); + + assert_eq!(actions.inserted_signers, 2); + assert_eq!(actions.inserted_non_signers, 4); + assert_eq!(actions.instructions.len(), 3); + + let is_encrypted = |ix: &dlp::args::MaybeEncryptedInstruction| { + !ix.data.suffix.as_bytes().is_empty() + }; + + assert!(!is_encrypted(&actions.instructions[0])); + assert!(is_encrypted(&actions.instructions[1])); + assert!(!is_encrypted(&actions.instructions[2])); +} diff --git a/tests/test_close_validator_fees_vault.rs b/tests/test_close_validator_fees_vault.rs index e0174b9d..6277012c 100644 --- a/tests/test_close_validator_fees_vault.rs +++ b/tests/test_close_validator_fees_vault.rs @@ -22,7 +22,7 @@ async fn test_close_validator_fees_vault() { validator_fees_vault_pda_from_validator(&validator.pubkey()); // Submit the close vault tx - let ix = dlp::instruction_builder::close_validator_fees_vault( + let ix = dlp_api::instruction_builder::close_validator_fees_vault( admin.pubkey(), admin.pubkey(), validator.pubkey(), diff --git a/tests/test_commit_fees_on_undelegation.rs b/tests/test_commit_fees_on_undelegation.rs index 9f6417e1..8283b289 100644 --- a/tests/test_commit_fees_on_undelegation.rs +++ b/tests/test_commit_fees_on_undelegation.rs @@ -53,7 +53,7 @@ async fn test_commit_fees_on_undelegation() { .min(record_rent + metadata_rent); let expected_fees_vault_fee = expected_total_fees / 10; - let ix = dlp::instruction_builder::undelegate( + let ix = dlp_api::instruction_builder::undelegate( validator.pubkey(), DELEGATED_PDA_ID, DELEGATED_PDA_OWNER_ID, diff --git a/tests/test_commit_finalize.rs b/tests/test_commit_finalize.rs index 1ba144be..6f1cf429 100644 --- a/tests/test_commit_finalize.rs +++ b/tests/test_commit_finalize.rs @@ -49,7 +49,7 @@ async fn run_test_commit_finalize( let new_account_balance = 1_000_000; - let (ix, pdas) = dlp::instruction_builder::commit_finalize( + let (ix, pdas) = dlp_api::instruction_builder::commit_finalize( authority.pubkey(), DELEGATED_PDA_ID, &mut CommitFinalizeArgs { @@ -82,7 +82,7 @@ async fn run_test_commit_finalize( let metadata = metadata.unwrap(); - assertables::assert_lt!( + assertables::assert_le!( metadata.compute_units_consumed, max_expected_cu ); @@ -110,7 +110,7 @@ async fn run_test_commit_finalize( ) .unwrap(); - assert_eq!(delegation_metadata.is_undelegatable, true); + assert!(delegation_metadata.is_undelegatable); } #[tokio::test] @@ -121,7 +121,7 @@ async fn test_commit_finalize_out_of_order() { let new_account_balance = 1_000_000; - let (ix, _pdas) = dlp::instruction_builder::commit_finalize( + let (ix, _pdas) = dlp_api::instruction_builder::commit_finalize( authority.pubkey(), DELEGATED_PDA_ID, &mut CommitFinalizeArgs { diff --git a/tests/test_commit_finalize_from_buffer.rs b/tests/test_commit_finalize_from_buffer.rs index 907b4a09..5bc18286 100644 --- a/tests/test_commit_finalize_from_buffer.rs +++ b/tests/test_commit_finalize_from_buffer.rs @@ -39,7 +39,7 @@ async fn test_commit_finalize_from_buffer_perf() { let state_buffer_pda = Pubkey::find_program_address(&[b"state_buffer"], &authority.pubkey()).0; - let (ix, pdas) = dlp::instruction_builder::commit_finalize_from_buffer( + let (ix, pdas) = dlp_api::instruction_builder::commit_finalize_from_buffer( authority.pubkey(), DELEGATED_PDA_ID, state_buffer_pda, @@ -93,7 +93,7 @@ async fn test_commit_finalize_from_buffer_perf() { ) .unwrap(); - assert_eq!(delegation_metadata.is_undelegatable, true); + assert!(delegation_metadata.is_undelegatable); } #[tokio::test] @@ -107,7 +107,7 @@ async fn test_commit_finalize_from_buffer_out_of_order() { Pubkey::find_program_address(&[b"state_buffer"], &authority.pubkey()).0; let new_account_balance = 1_000_000; - let (ix, _pdas) = dlp::instruction_builder::commit_finalize_from_buffer( + let (ix, _pdas) = dlp_api::instruction_builder::commit_finalize_from_buffer( authority.pubkey(), DELEGATED_PDA_ID, state_buffer_pda, diff --git a/tests/test_commit_on_curve.rs b/tests/test_commit_on_curve.rs index 47ac0f93..cf9daf77 100644 --- a/tests/test_commit_on_curve.rs +++ b/tests/test_commit_on_curve.rs @@ -41,7 +41,7 @@ async fn test_commit_on_curve() { }; // Commit the state for the delegated account - let ix = dlp::instruction_builder::commit_state( + let ix = dlp_api::instruction_builder::commit_state( validator.pubkey(), payer_delegated.pubkey(), system_program::ID, diff --git a/tests/test_commit_state.rs b/tests/test_commit_state.rs index e783446c..807f8a20 100644 --- a/tests/test_commit_state.rs +++ b/tests/test_commit_state.rs @@ -41,7 +41,7 @@ async fn test_commit_new_state() { }; // Commit the state for the delegated account - let ix = dlp::instruction_builder::commit_state( + let ix = dlp_api::instruction_builder::commit_state( authority.pubkey(), DELEGATED_PDA_ID, DELEGATED_PDA_OWNER_ID, @@ -118,7 +118,7 @@ async fn test_commit_out_of_order() { }; // Commit the state for the delegated account - let ix = dlp::instruction_builder::commit_state( + let ix = dlp_api::instruction_builder::commit_state( authority.pubkey(), DELEGATED_PDA_ID, DELEGATED_PDA_OWNER_ID, diff --git a/tests/test_commit_state_from_buffer.rs b/tests/test_commit_state_from_buffer.rs index e6901a64..3dfbe22b 100644 --- a/tests/test_commit_state_from_buffer.rs +++ b/tests/test_commit_state_from_buffer.rs @@ -44,7 +44,7 @@ async fn test_commit_new_state_from_buffer() { }; // Commit the state for the delegated account - let ix = dlp::instruction_builder::commit_state_from_buffer( + let ix = dlp_api::instruction_builder::commit_state_from_buffer( authority.pubkey(), DELEGATED_PDA_ID, DELEGATED_PDA_OWNER_ID, diff --git a/tests/test_commit_state_with_program_config.rs b/tests/test_commit_state_with_program_config.rs index eb3d2443..473d4afc 100644 --- a/tests/test_commit_state_with_program_config.rs +++ b/tests/test_commit_state_with_program_config.rs @@ -53,7 +53,7 @@ async fn test_commit_new_state(valid_config: bool) { }; // Commit the state for the delegated account - let ix = dlp::instruction_builder::commit_state( + let ix = dlp_api::instruction_builder::commit_state( authority.pubkey(), DELEGATED_PDA_ID, DELEGATED_PDA_OWNER_ID, diff --git a/tests/test_commit_undelegate_zero_lamports_system_owned.rs b/tests/test_commit_undelegate_zero_lamports_system_owned.rs index 2246742d..242a88ca 100644 --- a/tests/test_commit_undelegate_zero_lamports_system_owned.rs +++ b/tests/test_commit_undelegate_zero_lamports_system_owned.rs @@ -44,7 +44,7 @@ async fn test_commit_and_undelegate_zero_lamports_system_owned_account() { lamports: 0, }; - let ix_commit = dlp::instruction_builder::commit_state( + let ix_commit = dlp_api::instruction_builder::commit_state( validator.pubkey(), DELEGATED_PDA_ID, DELEGATED_PDA_OWNER_ID, @@ -59,11 +59,11 @@ async fn test_commit_and_undelegate_zero_lamports_system_owned_account() { let res_commit = banks.process_transaction(tx_commit).await; assert!(res_commit.is_ok()); - let ix_finalize = dlp::instruction_builder::finalize( + let ix_finalize = dlp_api::instruction_builder::finalize( validator.pubkey(), DELEGATED_PDA_ID, ); - let ix_undelegate = dlp::instruction_builder::undelegate( + let ix_undelegate = dlp_api::instruction_builder::undelegate( validator.pubkey(), DELEGATED_PDA_ID, DELEGATED_PDA_OWNER_ID, diff --git a/tests/test_delegate_on_curve.rs b/tests/test_delegate_on_curve.rs index 93efb27c..af9085a4 100644 --- a/tests/test_delegate_on_curve.rs +++ b/tests/test_delegate_on_curve.rs @@ -55,7 +55,7 @@ async fn test_delegate_on_curve() { assert_eq!(updated_alt_payer_account.owner, dlp::id()); // Submit the delegate tx - let ix = dlp::instruction_builder::delegate( + let ix = dlp_api::instruction_builder::delegate( payer.pubkey(), delegated_account, None, diff --git a/tests/test_delegate_with_actions.rs b/tests/test_delegate_with_actions.rs new file mode 100644 index 00000000..608dfe58 --- /dev/null +++ b/tests/test_delegate_with_actions.rs @@ -0,0 +1,224 @@ +use borsh::BorshDeserialize; +use dlp::{ + args::{DelegateArgs, DelegateWithActionsArgs}, + compact, +}; +use dlp_api::{ + instruction_builder::{ + delegate_with_actions, Encryptable, EncryptableFrom, + PostDelegationInstruction, + }, + Decrypt, +}; +use solana_program::{instruction::AccountMeta, pubkey::Pubkey}; +use solana_sdk::signer::Signer; + +#[test] +fn test_compact_account_meta_bit_packing() { + let packed = compact::AccountMeta::new_readonly(42, true); + assert_eq!(packed.key(), 42); + assert!(packed.is_signer()); + assert!(!packed.is_writable()); + + let packed = compact::AccountMeta::new(63, false); + assert_eq!(packed.key(), 63); + assert!(!packed.is_signer()); + assert!(packed.is_writable()); + + assert!(compact::AccountMeta::try_new(64, true, true).is_none()); +} + +#[test] +fn test_delegate_with_actions_borsh_roundtrip_compact_payload() { + let payer = Pubkey::new_unique(); + let signer = Pubkey::new_unique(); + + let instructions = vec![ + PostDelegationInstruction { + program_id: Pubkey::new_unique().cleartext(), + accounts: vec![ + AccountMeta::new_readonly(payer, true).cleartext(), + AccountMeta::new(Pubkey::new_unique(), false).cleartext(), + ], + data: vec![1, 2, 3].encrypted_from(3), + }, + PostDelegationInstruction { + program_id: Pubkey::new_unique().cleartext(), + accounts: vec![AccountMeta::new_readonly(signer, true).cleartext()], + data: vec![9, 9].encrypted_from(2), + }, + ]; + + let ix = delegate_with_actions( + payer, + Pubkey::new_unique(), + Some(Pubkey::new_unique()), + DelegateArgs { + commit_frequency_ms: 500, + seeds: vec![b"seed-a".to_vec()], + validator: Some(Pubkey::new_unique()), + }, + instructions, + ); + + let args: DelegateWithActionsArgs = + DelegateWithActionsArgs::try_from_slice(&ix.data[8..]).unwrap(); + assert_eq!(args.delegate.commit_frequency_ms, 500); + assert_eq!(args.actions.signers.len(), 2); + assert_eq!(args.actions.instructions.len(), 2); + assert!( + args.actions.signers.len() + args.actions.non_signers.len() + <= compact::MAX_PUBKEYS as usize + ); +} + +#[test] +fn test_delegate_with_actions_builder_adds_compact_signers_to_remaining_accounts( +) { + use solana_sdk::signature::Keypair; + + let validator = Keypair::new(); + let payer = Pubkey::new_unique(); + let delegated_account = Pubkey::new_unique(); + let owner = Pubkey::new_unique(); + let signer_a = Pubkey::new_unique(); + let signer_b = Pubkey::new_unique(); + + let instructions = vec![ + PostDelegationInstruction { + program_id: Pubkey::new_unique().cleartext(), + accounts: vec![ + AccountMeta::new_readonly(signer_a, true).cleartext(), + AccountMeta::new_readonly(signer_b, true).cleartext(), + ], + data: vec![7, 7].encrypted_from(2), + }, + PostDelegationInstruction { + program_id: Pubkey::new_unique().cleartext(), + accounts: vec![ + AccountMeta::new_readonly(signer_a, true).cleartext(), + AccountMeta::new(Pubkey::new_unique(), false).cleartext(), + ], + data: vec![8, 8].encrypted_from(2), + }, + ]; + + let ix = delegate_with_actions( + payer, + delegated_account, + Some(owner), + DelegateArgs { + validator: Some(validator.pubkey()), + ..Default::default() + }, + instructions, + ); + + // first 7 are the required delegate_with_actions accounts + let remaining = &ix.accounts[7..]; + assert_eq!(remaining.len(), 2); + assert!(remaining.iter().all(|a| a.is_signer && !a.is_writable)); + assert!(remaining.iter().any(|a| a.pubkey == signer_a)); + assert!(remaining.iter().any(|a| a.pubkey == signer_b)); +} + +#[test] +fn test_delegate_with_actions_builder_private_sets_encrypted_payload() { + use dlp_api::encryption; + use solana_sdk::signature::Keypair; + + let validator = Keypair::new(); + let validator_x25519_secret = + encryption::keypair_to_x25519_secret(&validator).unwrap(); + let validator_x25519_pubkey = + encryption::ed25519_pubkey_to_x25519(validator.pubkey().as_array()) + .unwrap(); + + let payer = Pubkey::new_unique(); + let signer = Pubkey::new_unique(); + let instructions = vec![PostDelegationInstruction { + program_id: Pubkey::new_unique().cleartext(), + accounts: vec![AccountMeta::new_readonly(signer, true).cleartext()], + data: vec![4, 2].encrypted_from(1), + }]; + + let ix = delegate_with_actions( + payer, + Pubkey::new_unique(), + Some(Pubkey::new_unique()), + DelegateArgs { + validator: Some(validator.pubkey()), + ..Default::default() + }, + instructions, + ); + + let args: DelegateWithActionsArgs = + DelegateWithActionsArgs::try_from_slice(&ix.data[8..]).unwrap(); + assert_eq!(args.actions.signers.len(), 1); + let ix = &args.actions.instructions[0]; + assert_eq!(ix.data.prefix, vec![4]); + let decrypted = encryption::decrypt( + ix.data.suffix.as_bytes(), + &validator_x25519_pubkey, + &validator_x25519_secret, + ) + .unwrap(); + assert_eq!(decrypted, vec![2]); +} + +#[test] +fn test_delegate_with_actions_builder_encrypts_and_decrypts_accounts_and_data() +{ + use solana_sdk::signature::Keypair; + + let validator = Keypair::new(); + let payer = Pubkey::new_unique(); + let signer = Pubkey::new_unique(); + let nonsigner = Pubkey::new_unique(); + let program_id = Pubkey::new_unique(); + + let instructions = vec![PostDelegationInstruction { + program_id: program_id.cleartext(), + accounts: vec![ + AccountMeta::new_readonly(signer, true).cleartext(), + AccountMeta::new_readonly(nonsigner, false).encrypted(), + ], + data: vec![7, 8, 9].encrypted_from(1), + }]; + + let ix = delegate_with_actions( + payer, + Pubkey::new_unique(), + Some(Pubkey::new_unique()), + DelegateArgs { + validator: Some(validator.pubkey()), + ..Default::default() + }, + instructions, + ); + + let args: DelegateWithActionsArgs = + DelegateWithActionsArgs::try_from_slice(&ix.data[8..]).unwrap(); + let ix = &args.actions.instructions[0]; + + let clear_meta = match ix.accounts[0] { + dlp::args::MaybeEncryptedAccountMeta::ClearText(meta) => meta, + _ => panic!("expected cleartext account meta for signer"), + }; + assert_eq!(clear_meta.key(), 0); + assert!(clear_meta.is_signer()); + assert!(!clear_meta.is_writable()); + + let decrypted_meta = ix.accounts[1] + .clone() + .decrypt_with_keypair(&validator) + .unwrap(); + assert_eq!(decrypted_meta.key(), 2); + assert!(!decrypted_meta.is_signer()); + assert!(!decrypted_meta.is_writable()); + + let decrypted_data = + ix.data.clone().decrypt_with_keypair(&validator).unwrap(); + assert_eq!(decrypted_data, vec![7, 8, 9]); +} diff --git a/tests/test_delegation_confined_accounts.rs b/tests/test_delegation_confined_accounts.rs index eac2f9b5..4ddcc871 100644 --- a/tests/test_delegation_confined_accounts.rs +++ b/tests/test_delegation_confined_accounts.rs @@ -34,7 +34,7 @@ async fn test_delegation_confined_accounts_rejects_system_validator() { let assign_res = banks.process_transaction(assign_tx).await; assert!(assign_res.is_ok()); - let ix = dlp::instruction_builder::delegate( + let ix = dlp_api::instruction_builder::delegate( payer.pubkey(), delegated.pubkey(), None, @@ -85,7 +85,7 @@ async fn test_delegation_confined_accounts_allows_system_validator() { let assign_res = banks.process_transaction(assign_tx).await; assert!(assign_res.is_ok()); - let ix = dlp::instruction_builder::delegate_with_any_validator( + let ix = dlp_api::instruction_builder::delegate_with_any_validator( payer.pubkey(), delegated.pubkey(), None, diff --git a/tests/test_finalize.rs b/tests/test_finalize.rs index 08cfe691..d4354fd7 100644 --- a/tests/test_finalize.rs +++ b/tests/test_finalize.rs @@ -52,7 +52,7 @@ async fn test_finalize() { let new_state_data_before_finalize = new_state_before_finalize.data.clone(); // Submit the finalize tx - let ix = dlp::instruction_builder::finalize( + let ix = dlp_api::instruction_builder::finalize( authority.pubkey(), DELEGATED_PDA_ID, ); diff --git a/tests/test_init_fees_vault.rs b/tests/test_init_fees_vault.rs index 48e2919e..6c479400 100644 --- a/tests/test_init_fees_vault.rs +++ b/tests/test_init_fees_vault.rs @@ -16,7 +16,8 @@ async fn test_init_fees_vault() { // Setup let (banks, payer, _, blockhash) = setup_program_test_env().await; - let ix = dlp::instruction_builder::init_protocol_fees_vault(payer.pubkey()); + let ix = + dlp_api::instruction_builder::init_protocol_fees_vault(payer.pubkey()); let tx = Transaction::new_signed_with_payer( &[ix], Some(&payer.pubkey()), diff --git a/tests/test_init_validator_fees_vault.rs b/tests/test_init_validator_fees_vault.rs index 3505ce48..67db1bb9 100644 --- a/tests/test_init_validator_fees_vault.rs +++ b/tests/test_init_validator_fees_vault.rs @@ -19,7 +19,7 @@ async fn test_init_validator_fees_vault() { let (banks, payer, admin, blockhash) = setup_program_test_env().await; let validator_identity = Pubkey::new_unique(); - let ix = dlp::instruction_builder::init_validator_fees_vault( + let ix = dlp_api::instruction_builder::init_validator_fees_vault( payer.pubkey(), admin.pubkey(), validator_identity, @@ -42,7 +42,7 @@ async fn test_init_validator_fees_vault() { // Assert record cannot be created if the admin is not the correct one let validator_identity = Pubkey::new_unique(); - let ix = dlp::instruction_builder::init_validator_fees_vault( + let ix = dlp_api::instruction_builder::init_validator_fees_vault( payer.pubkey(), payer.pubkey(), validator_identity, diff --git a/tests/test_lamports_settlement.rs b/tests/test_lamports_settlement.rs index d95a4ea7..4b476dd2 100644 --- a/tests/test_lamports_settlement.rs +++ b/tests/test_lamports_settlement.rs @@ -421,7 +421,7 @@ async fn undelegate(args: UndelegateArgs<'_>) { delegation_record_pda_from_delegated_account(&args.delegated_account); // Submit the undelegate tx - let ix = dlp::instruction_builder::undelegate( + let ix = dlp_api::instruction_builder::undelegate( args.authority.pubkey(), args.delegated_account, args.owner_program, @@ -470,7 +470,7 @@ struct FinalizeNewStateArgs<'a> { } async fn finalize_new_state(args: FinalizeNewStateArgs<'_>) { - let ix = dlp::instruction_builder::finalize( + let ix = dlp_api::instruction_builder::finalize( args.authority.pubkey(), args.delegated_account, ); @@ -516,7 +516,7 @@ async fn commit_new_state(args: CommitNewStateArgs<'_>) { }; // Commit the state for the delegated account - let ix = dlp::instruction_builder::commit_state( + let ix = dlp_api::instruction_builder::commit_state( args.authority.pubkey(), args.delegated_account, args.delegated_account_owner, diff --git a/tests/test_protocol_claim_fees.rs b/tests/test_protocol_claim_fees.rs index 152c508e..61ce1e2f 100644 --- a/tests/test_protocol_claim_fees.rs +++ b/tests/test_protocol_claim_fees.rs @@ -21,7 +21,7 @@ async fn test_protocol_claim_fees() { let fees_vault_pda = fees_vault_pda(); // Submit the claim fees tx - let ix = dlp::instruction_builder::protocol_claim_fees(admin.pubkey()); + let ix = dlp_api::instruction_builder::protocol_claim_fees(admin.pubkey()); let tx = Transaction::new_signed_with_payer( &[ix], Some(&payer.pubkey()), diff --git a/tests/test_top_up.rs b/tests/test_top_up.rs index c3e7db90..c6f644ab 100644 --- a/tests/test_top_up.rs +++ b/tests/test_top_up.rs @@ -31,7 +31,7 @@ async fn test_top_up_ephemeral_balance() { // Setup let (banks, payer, _, blockhash) = setup_program_test_env().await; - let ix = dlp::instruction_builder::top_up_ephemeral_balance( + let ix = dlp_api::instruction_builder::top_up_ephemeral_balance( payer.pubkey(), payer.pubkey(), None, @@ -66,7 +66,7 @@ async fn test_top_up_ephemeral_balance_for_pubkey() { let pubkey = Keypair::new().pubkey(); - let ix = dlp::instruction_builder::top_up_ephemeral_balance( + let ix = dlp_api::instruction_builder::top_up_ephemeral_balance( payer.pubkey(), pubkey, None, @@ -99,14 +99,14 @@ async fn test_top_up_ephemeral_balance_and_delegate() { let (banks, payer, _, blockhash) = setup_program_test_env().await; // Top-up Ix - let ix = dlp::instruction_builder::top_up_ephemeral_balance( + let ix = dlp_api::instruction_builder::top_up_ephemeral_balance( payer.pubkey(), payer.pubkey(), None, None, ); // Delegate ephemeral balance Ix - let delegate_ix = dlp::instruction_builder::delegate_ephemeral_balance( + let delegate_ix = dlp_api::instruction_builder::delegate_ephemeral_balance( payer.pubkey(), payer.pubkey(), DelegateEphemeralBalanceArgs::default(), @@ -158,14 +158,14 @@ async fn test_top_up_ephemeral_balance_and_delegate_for_pubkey() { let pubkey = key.pubkey(); // Top-up Ix - let ix = dlp::instruction_builder::top_up_ephemeral_balance( + let ix = dlp_api::instruction_builder::top_up_ephemeral_balance( payer.pubkey(), pubkey, None, None, ); // Delegate ephemeral balance Ix - let delegate_ix = dlp::instruction_builder::delegate_ephemeral_balance( + let delegate_ix = dlp_api::instruction_builder::delegate_ephemeral_balance( payer.pubkey(), pubkey, DelegateEphemeralBalanceArgs::default(), @@ -210,7 +210,7 @@ async fn test_undelegate() { assert_eq!(ephemeral_balance_owner, dlp::id()); // Undelegate ephemeral balance Ix - let ix = dlp::instruction_builder::undelegate( + let ix = dlp_api::instruction_builder::undelegate( validator.pubkey(), ephemeral_balance_pda, system_program::id(), @@ -260,14 +260,14 @@ async fn test_undelegate_and_close() { .lamports; // Undelegate ephemeral balance Ix - let ix = dlp::instruction_builder::undelegate( + let ix = dlp_api::instruction_builder::undelegate( validator.pubkey(), ephemeral_balance_pda, system_program::id(), validator.pubkey(), ); - let ix_close = dlp::instruction_builder::close_ephemeral_balance( + let ix_close = dlp_api::instruction_builder::close_ephemeral_balance( payer_alt.pubkey(), 0, ); diff --git a/tests/test_undelegate.rs b/tests/test_undelegate.rs index 0a26135c..54669b15 100644 --- a/tests/test_undelegate.rs +++ b/tests/test_undelegate.rs @@ -40,13 +40,13 @@ async fn test_finalize_and_undelegate() { let new_state_data_before_finalize = new_state_before_finalize.data.clone(); // Create the finalize tx - let ix_finalize = dlp::instruction_builder::finalize( + let ix_finalize = dlp_api::instruction_builder::finalize( authority.pubkey(), DELEGATED_PDA_ID, ); // Create the undelegate tx - let ix_undelegate = dlp::instruction_builder::undelegate( + let ix_undelegate = dlp_api::instruction_builder::undelegate( authority.pubkey(), DELEGATED_PDA_ID, DELEGATED_PDA_OWNER_ID, diff --git a/tests/test_undelegate_confined_account.rs b/tests/test_undelegate_confined_account.rs index 52021520..7724caa3 100644 --- a/tests/test_undelegate_confined_account.rs +++ b/tests/test_undelegate_confined_account.rs @@ -30,7 +30,7 @@ async fn test_undelegate_confined_account() { let data_before = delegated_before.data.clone(); // Submit the admin-only undelegate (confined) tx - let ix = dlp::instruction_builder::undelegate_confined_account( + let ix = dlp_api::instruction_builder::undelegate_confined_account( admin.pubkey(), DELEGATED_PDA_ID, DELEGATED_PDA_OWNER_ID, diff --git a/tests/test_undelegate_on_curve.rs b/tests/test_undelegate_on_curve.rs index cfa783ef..c6256c16 100644 --- a/tests/test_undelegate_on_curve.rs +++ b/tests/test_undelegate_on_curve.rs @@ -32,7 +32,7 @@ async fn test_undelegate_on_curve() { ); // Submit the undelegate tx - let ix = dlp::instruction_builder::undelegate( + let ix = dlp_api::instruction_builder::undelegate( validator.pubkey(), delegated_on_curve.pubkey(), system_program::id(), diff --git a/tests/test_undelegate_without_commit.rs b/tests/test_undelegate_without_commit.rs index 87a7a5d5..456c7e37 100644 --- a/tests/test_undelegate_without_commit.rs +++ b/tests/test_undelegate_without_commit.rs @@ -39,7 +39,7 @@ async fn test_undelegate_without_commit() { delegated_pda_state_before_undelegation.data.clone(); // Submit the undelegate tx - let ix = dlp::instruction_builder::undelegate( + let ix = dlp_api::instruction_builder::undelegate( validator.pubkey(), DELEGATED_PDA_ID, DELEGATED_PDA_OWNER_ID, diff --git a/tests/test_validator_claim_fees.rs b/tests/test_validator_claim_fees.rs index dc153eb6..e29d57e7 100644 --- a/tests/test_validator_claim_fees.rs +++ b/tests/test_validator_claim_fees.rs @@ -47,7 +47,7 @@ async fn test_validator_claim_fees() { // Submit the withdrawal tx let withdrawal_amount = 100000; - let ix = dlp::instruction_builder::validator_claim_fees( + let ix = dlp_api::instruction_builder::validator_claim_fees( validator.pubkey(), Some(withdrawal_amount), ); diff --git a/tests/test_whitelist_validator_for_program.rs b/tests/test_whitelist_validator_for_program.rs index 79b90750..80c64541 100644 --- a/tests/test_whitelist_validator_for_program.rs +++ b/tests/test_whitelist_validator_for_program.rs @@ -18,7 +18,7 @@ async fn test_whitelist_validator_for_program() { // Setup let (banks, _, validator, blockhash) = setup_program_test_env().await; - let ix = dlp::instruction_builder::whitelist_validator_for_program( + let ix = dlp_api::instruction_builder::whitelist_validator_for_program( validator.pubkey(), validator.pubkey(), DELEGATED_PDA_OWNER_ID, @@ -52,7 +52,7 @@ async fn test_remove_validator_for_program() { // Setup let (banks, _, validator, blockhash) = setup_program_test_env().await; - let ix = dlp::instruction_builder::whitelist_validator_for_program( + let ix = dlp_api::instruction_builder::whitelist_validator_for_program( validator.pubkey(), validator.pubkey(), DELEGATED_PDA_OWNER_ID, @@ -69,7 +69,7 @@ async fn test_remove_validator_for_program() { assert!(res.is_ok()); // Remove the validator - let ix = dlp::instruction_builder::whitelist_validator_for_program( + let ix = dlp_api::instruction_builder::whitelist_validator_for_program( validator.pubkey(), validator.pubkey(), DELEGATED_PDA_OWNER_ID,