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..4b92d1fd 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" @@ -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" @@ -1182,12 +1238,42 @@ dependencies = [ "syn 2.0.93", ] +[[package]] +name = "dlp-api" +version = "0.1.0" +dependencies = [ + "bincode", + "borsh 1.5.7", + "libsodium-rs", + "magicblock-delegation-program", + "rand 0.8.5", + "serde", + "solana-program", + "solana-sdk", + "thiserror 1.0.69", +] + [[package]] name = "downcast" 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 +1467,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 +1508,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 +1532,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 +1764,7 @@ dependencies = [ "futures-core", "futures-sink", "futures-util", - "http", + "http 0.2.12", "indexmap", "slab", "tokio", @@ -1712,6 +1811,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 +1896,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 +1913,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 +1946,7 @@ dependencies = [ "futures-core", "futures-util", "h2", - "http", + "http 0.2.12", "http-body", "httparse", "httpdate", @@ -1846,7 +1966,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 +2325,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 +2408,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 +2476,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" @@ -2332,6 +2508,7 @@ dependencies = [ "borsh 1.5.7", "bytemuck", "const-crypto", + "dlp-api", "magicblock-delegation-program", "num_enum", "pinocchio 0.10.1", @@ -2414,6 +2591,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 +2606,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" @@ -2943,9 +3136,9 @@ dependencies = [ [[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 +3543,7 @@ dependencies = [ "futures-core", "futures-util", "h2", - "http", + "http 0.2.12", "http-body", "hyper", "hyper-rustls", @@ -3389,7 +3582,7 @@ checksum = "5a735987236a8e238bf0296c7e351b999c188ccc11477f311b82b55c93984216" dependencies = [ "anyhow", "async-trait", - "http", + "http 0.2.12", "reqwest", "serde", "task-local-extensions", @@ -3440,6 +3633,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 +3889,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 +3917,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 +4063,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" @@ -7055,9 +7260,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 +7661,7 @@ dependencies = [ "byteorder", "bytes", "data-encoding", - "http", + "http 0.2.12", "httparse", "log", "rand 0.8.5", @@ -7468,6 +7673,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 +7740,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 +8366,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..2e25f0d6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,13 +11,27 @@ 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"] +sdk = [ + "no-entrypoint", + "dep:solana-sdk", + "dep:rand", + "dep:pinocchio", + "dep:pinocchio-log", + "dep:pinocchio-pubkey", + "dep:pinocchio-system", + "dep:const-crypto", + "dep:rkyv", + "dep:solana-curve25519", +] program = [ "dep:pinocchio", "dep:pinocchio-log", @@ -60,11 +74,15 @@ 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 = { path = "dlp-api" } rand = { version = "=0.8.5", features = ["small_rng"] } solana-program-test = ">=1.16" solana-sdk = ">=1.16" diff --git a/Makefile b/Makefile index 52a4976d..1a06852e 100644 --- a/Makefile +++ b/Makefile @@ -4,5 +4,8 @@ build: test: RUST_LOG=off cargo test-sbf --features unit_test_config +lint: + cargo clippy -- --deny=warnings + fmt: cargo +nightly fmt diff --git a/dlp-api/Cargo.toml b/dlp-api/Cargo.toml new file mode 100644 index 00000000..a2d6657d --- /dev/null +++ b/dlp-api/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "dlp-api" +version = "0.1.0" +edition = "2021" +license = "MIT" + +[lib] +name = "dlp_api" + +[dependencies] +# core program crate +dlp = { package = "magicblock-delegation-program", path = ".." } + +# sdk deps +solana-program = { version = ">=1.16, <3.0.0" } +solana-sdk = { version = ">=1.16" } +rand = { version = "=0.8.5", features = ["small_rng"] } +libsodium-rs = { version = "0.2.0" } + +borsh = { version = "1.5.3", features = ["derive"] } +bincode = { version = "^1.3" } +serde = { version = "1.0.228", features = ["derive"] } +thiserror = { version = ">=1" } + +[dev-dependencies] +dlp = { package = "magicblock-delegation-program", path = "..", features = ["unit_test_config"] } 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..a8e9451a --- /dev/null +++ b/dlp-api/src/cpi/delegate_with_actions.rs @@ -0,0 +1,67 @@ +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, +}; + +// TODO (snawaz): review it as it is not done yet. jus the basic idea is implemented. + +pub fn delegate_with_actions( + payer: Pubkey, + delegated_account: Pubkey, + owner: Option, + delegate: DelegateArgs, + actions: PostDelegationActions, + actions_signers: Vec, +) -> Instruction { + 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(&bincode::serialize(&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/encryption/mod.rs b/dlp-api/src/encryption/mod.rs new file mode 100644 index 00000000..d50e5e37 --- /dev/null +++ b/dlp-api/src/encryption/mod.rs @@ -0,0 +1,132 @@ +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> { + 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 96% rename from src/instruction_builder/commit_diff.rs rename to dlp-api/src/instruction_builder/commit_diff.rs index f0cacc1d..282322ed 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,7 +40,7 @@ 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_readonly(delegated_account, 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 96% rename from src/instruction_builder/commit_diff_from_buffer.rs rename to dlp-api/src/instruction_builder/commit_diff_from_buffer.rs index 85aafb09..2b53398b 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,7 +41,7 @@ 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_readonly(delegated_account, false), diff --git a/src/instruction_builder/commit_finalize.rs b/dlp-api/src/instruction_builder/commit_finalize.rs similarity index 94% rename from src/instruction_builder/commit_finalize.rs rename to dlp-api/src/instruction_builder/commit_finalize.rs index d1d83fd5..d4cb4434 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,7 +51,7 @@ pub fn commit_finalize( ( 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/commit_finalize_from_buffer.rs b/dlp-api/src/instruction_builder/commit_finalize_from_buffer.rs similarity index 94% rename from src/instruction_builder/commit_finalize_from_buffer.rs rename to dlp-api/src/instruction_builder/commit_finalize_from_buffer.rs index 77439ea5..12864350 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,7 +45,7 @@ pub fn commit_finalize_from_buffer( ( 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/commit_state.rs b/dlp-api/src/instruction_builder/commit_state.rs similarity index 96% rename from src/instruction_builder/commit_state.rs rename to dlp-api/src/instruction_builder/commit_state.rs index c30eb74d..d70e6c53 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,7 +40,7 @@ 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_readonly(delegated_account, 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 96% rename from src/instruction_builder/commit_state_from_buffer.rs rename to dlp-api/src/instruction_builder/commit_state_from_buffer.rs index b455b14c..2971167e 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,7 +41,7 @@ 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_readonly(delegated_account, 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..b96d603b --- /dev/null +++ b/dlp-api/src/instruction_builder/delegate_with_actions.rs @@ -0,0 +1,314 @@ +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::{EncryptableAccountMeta, PostDelegationInstruction}; + +/// See [dlp::processor::process_delegate_with_actions] for docs. +pub fn delegate_with_actions( + payer: Pubkey, + delegated_account: Pubkey, + owner: Option, + delegate: DelegateArgs, + actions: Vec, +) -> Instruction { + let (actions, signers) = + create_post_delegation_actions(actions, delegate.validator); + + 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(&bincode::serialize(&args).unwrap()); + data + }, + } +} + +pub fn create_post_delegation_actions( + instructions: Vec, + validator: Option, +) -> (dlp::args::PostDelegationActions, Vec) { + 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 instructions + .iter() + .flat_map(|ix| ix.accounts.iter()) + .filter(|meta| meta.account_meta.is_signer) + { + add_to_signers(meta); + } + + for ix in instructions.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) + .unwrap() as u8 + }; + + let compact_instructions: Vec = instructions + .into_iter() + .map(|ix| MaybeEncryptedInstruction { + program_id: index_of(&ix.program_id.pubkey), + + accounts: ix + .accounts + .into_iter() + .map(|meta| { + dlp::compact::AccountMeta::try_new( + index_of(&meta.account_meta.pubkey), + meta.account_meta.is_signer, + meta.account_meta.is_writable, + ) + .expect("compact account index must fit in 6 bits") + }) + .collect(), + + data: ix.data.encrypt(&validator), + }) + .collect(); + + ( + dlp::args::PostDelegationActions { + signers: signers.iter().map(|s| s.pubkey).collect(), + + non_signers: non_signers + .into_iter() + .map(|ns| ns.encrypt(&validator)) + .collect(), + + instructions: compact_instructions, + }, + signers, + ) +} + +#[cfg(test)] +mod tests { + use dlp::args::MaybeEncryptedPubkey; + 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) = create_post_delegation_actions( + instructions, + Some(validator.pubkey()), + ); + + // indices: a, c, e, d, b + // 0, 1, 2, 3, 4 + + assert_eq!(actions.signers.len(), 3); + assert_eq!(actions.signers[0], a); // signer + assert_eq!(actions.signers[1], c); // signer + assert_eq!(actions.signers[2], e); // signer + + if false { + let non_signer_pubkeys: Vec<_> = actions + .non_signers + .iter() + .map(|key| match key { + MaybeEncryptedPubkey::ClearText(pubkey) => *pubkey, + MaybeEncryptedPubkey::Encrypted(_) => { + panic!("there must not be any encrypted pubkeys") + } + }) + .collect(); + + assert_eq!(non_signer_pubkeys[0], d); // non-signer + assert_eq!(non_signer_pubkeys[1], b); // non-signer + } else { + 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 + assert_eq!(actions.instructions[0].accounts[0].key(), 0); // a + assert_eq!(actions.instructions[0].accounts[1].key(), 1); // c + assert_eq!(actions.instructions[0].accounts[2].key(), 4); // b + assert_eq!(actions.instructions[0].accounts[3].key(), 2); // e + assert_eq!(actions.instructions[0].accounts[4].key(), 3); // d + } + + #[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..fc55183d --- /dev/null +++ b/dlp-api/src/instruction_builder/types/encryptable_types.rs @@ -0,0 +1,149 @@ +use dlp::args::{EncryptedBuffer, MaybeEncryptedIxData, MaybeEncryptedPubkey}; +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, +} + +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), + } + } + } +} + +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 +#[derive(Clone, Debug)] +pub struct EncryptableAccountMeta { + pub account_meta: AccountMeta, + pub is_encryptable: bool, +} + +impl EncryptableAccountMeta { + pub fn encrypt(self, validator: &Option) -> MaybeEncryptedPubkey { + if self.is_encryptable { + let validator = validator.expect(""); + MaybeEncryptedPubkey::Encrypted(EncryptedBuffer::new( + crate::encryption::encrypt_ed25519_recipient( + self.account_meta.pubkey.as_array(), + validator.as_array(), + ) + .expect(""), + )) + } else { + MaybeEncryptedPubkey::ClearText(self.account_meta.pubkey) + } + } +} + +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 EncryptableIxData { + pub fn encrypt(self, validator: &Option) -> MaybeEncryptedIxData { + if self.encrypt_begin_offset >= self.data.len() { + MaybeEncryptedIxData { + prefix: self.data, + suffix: EncryptedBuffer::default(), + } + } else { + let validator = validator.expect(""); + 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(), + ) + .expect(""), + ), + } + } + } +} + +impl EncryptableFrom for Vec { + type Output = EncryptableIxData; + fn encrypted_from(self, offset: usize) -> Self::Output { + EncryptableIxData { + data: self, + encrypt_begin_offset: offset, + } + } +} 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..01f117aa --- /dev/null +++ b/dlp-api/src/instruction_builder/types/mod.rs @@ -0,0 +1,19 @@ +mod encryptable_types; + +pub use encryptable_types::*; + +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; +} 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..5bc79256 --- /dev/null +++ b/dlp-api/src/lib.rs @@ -0,0 +1,5 @@ +pub use dlp; + +pub mod cpi; +pub mod encryption; +pub mod instruction_builder; diff --git a/src/args/delegate.rs b/src/args/delegate.rs index 305b5e51..c438625d 100644 --- a/src/args/delegate.rs +++ b/src/args/delegate.rs @@ -1,7 +1,10 @@ use borsh::{BorshDeserialize, BorshSerialize}; +use serde::{Deserialize, Serialize}; use solana_program::pubkey::Pubkey; -#[derive(Default, Debug, BorshSerialize, BorshDeserialize)] +#[derive( + Default, Debug, BorshSerialize, BorshDeserialize, Serialize, Deserialize, +)] pub struct DelegateArgs { /// The frequency at which the validator should commit the account data /// if no commit is triggered by the owning program diff --git a/src/args/delegate_with_actions.rs b/src/args/delegate_with_actions.rs new file mode 100644 index 00000000..b8380339 --- /dev/null +++ b/src/args/delegate_with_actions.rs @@ -0,0 +1,61 @@ +use serde::{Deserialize, Serialize}; +use solana_program::pubkey::Pubkey; + +use super::DelegateArgs; +use crate::compact; + +#[derive(Debug, Serialize, Deserialize)] +pub struct DelegateWithActionsArgs { + /// Standard delegation parameters. + pub delegate: DelegateArgs, + + /// Compact post-delegation actions. + pub actions: PostDelegationActions, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct PostDelegationActions { + pub signers: Vec, + + pub non_signers: Vec, + + pub instructions: Vec, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct MaybeEncryptedInstruction { + pub program_id: u8, + + pub accounts: Vec, + + pub data: MaybeEncryptedIxData, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum MaybeEncryptedPubkey { + ClearText(Pubkey), + Encrypted(EncryptedBuffer), +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct MaybeEncryptedIxData { + pub prefix: Vec, + pub suffix: EncryptedBuffer, +} + +#[derive(Clone, Debug, Default, Serialize, Deserialize)] +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 + } +} 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..c1c58e6e --- /dev/null +++ b/src/compact/account_meta.rs @@ -0,0 +1,60 @@ +use serde::{Deserialize, Serialize}; + +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 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"); + } +} diff --git a/src/compact/instruction.rs b/src/compact/instruction.rs new file mode 100644 index 00000000..271711ff --- /dev/null +++ b/src/compact/instruction.rs @@ -0,0 +1,39 @@ +use serde::{Deserialize, Serialize}; + +use crate::compact; + +#[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, + } + } +} diff --git a/src/compact/mod.rs b/src/compact/mod.rs new file mode 100644 index 00000000..a33bfc45 --- /dev/null +++ b/src/compact/mod.rs @@ -0,0 +1,5 @@ +mod account_meta; +mod instruction; + +pub use account_meta::*; +pub use instruction::*; 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..9b2802e2 100644 --- a/src/error.rs +++ b/src/error.rs @@ -159,14 +159,14 @@ impl From for ProgramError { } } -#[cfg(not(feature = "sdk"))] +//#[cfg(not(feature = "sdk"))] impl From for pinocchio::error::ProgramError { fn from(e: DlpError) -> Self { pinocchio::error::ProgramError::Custom(e as u32) } } -#[cfg(not(feature = "sdk"))] +//#[cfg(not(feature = "sdk"))] 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..877d9de8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,15 +1,15 @@ #![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")] @@ -24,10 +24,10 @@ 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 state; @@ -36,16 +36,16 @@ mod account_size_class; pub use account_size_class::*; -#[cfg(not(feature = "sdk"))] +//#[cfg(not(feature = "sdk"))] mod diff; -#[cfg(not(feature = "sdk"))] +//#[cfg(not(feature = "sdk"))] mod processor; -#[cfg(not(feature = "sdk"))] +//#[cfg(not(feature = "sdk"))] pub use diff::*; // re-export -#[cfg(not(feature = "sdk"))] +//#[cfg(not(feature = "sdk"))] pub use rkyv; #[cfg(feature = "log-cost")] @@ -56,7 +56,7 @@ mod entrypoint; declare_id!("DELeGGvXpWV2fqJUhqcF5ZSYMS4JTLjteaAMARRSaeSh"); -#[cfg(not(feature = "sdk"))] +//#[cfg(not(feature = "sdk"))] pub mod fast { pinocchio::address::declare_id!( "DELeGGvXpWV2fqJUhqcF5ZSYMS4JTLjteaAMARRSaeSh" @@ -110,6 +110,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), ), 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/delegate_with_actions.rs b/src/processor/fast/delegate_with_actions.rs new file mode 100644 index 00000000..38f11b13 --- /dev/null +++ b/src/processor/fast/delegate_with_actions.rs @@ -0,0 +1,299 @@ +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, + consts::{DEFAULT_VALIDATOR_IDENTITY, RENT_EXCEPTION_ZERO_BYTES_LAMPORTS}, + 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, + }, + }, + }, + utils::curve::is_on_curve_fast, + }, + require, require_n_accounts_with_optionals, + 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 = bincode::deserialize(data) + .map_err(|_| ProgramError::InvalidInstructionData)?; + + // Validate instruction payload shape up-front. This confirms delegate args + // and actions envelope format, while encrypted bytes remain opaque. + { + 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 { + require!( + (account.is_signer() && account.key() < signers_count) + || account.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.to_bytes() + }) + .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 = bincode::serialize(&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/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/utils/requires.rs b/src/processor/fast/utils/requires.rs index 3bee15df..e7a88a0c 100644 --- a/src/processor/fast/utils/requires.rs +++ b/src/processor/fast/utils/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) => {{ 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/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_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..1cec81a9 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 { @@ -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..06d49fad 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, @@ -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..a69d0406 --- /dev/null +++ b/tests/test_delegate_with_actions.rs @@ -0,0 +1,158 @@ +use dlp::{ + args::{DelegateArgs, DelegateWithActionsArgs}, + compact, +}; +use dlp_api::instruction_builder::{ + delegate_with_actions, Encryptable, EncryptableFrom, + PostDelegationInstruction, +}; +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_bincode_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 = + bincode::deserialize(&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( +) { + 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::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 = + bincode::deserialize(&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]); +} 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,