diff --git a/.gitignore b/.gitignore index a98c68e..2d661d8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ CLAUDE.md target/ */target/ -/.claude \ No newline at end of file +/.claude +/prompts \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 7ee8823..ba8cd4b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -51,6 +51,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", + "getrandom 0.3.4", "once_cell", "version_check", "zerocopy", @@ -71,6 +72,640 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" +[[package]] +name = "alloy" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59febb24956a41c29bb5f450978fbe825bd6456b3f80586c8bd558dc882e7b6a" +dependencies = [ + "alloy-consensus", + "alloy-contract", + "alloy-core", + "alloy-eips", + "alloy-genesis", + "alloy-network", + "alloy-provider", + "alloy-pubsub", + "alloy-rpc-client", + "alloy-rpc-types", + "alloy-serde", + "alloy-signer", + "alloy-signer-local", + "alloy-transport", + "alloy-transport-http", + "alloy-transport-ipc", + "alloy-transport-ws", +] + +[[package]] +name = "alloy-chains" +version = "0.1.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28e2652684758b0d9b389d248b209ed9fd9989ef489a550265fe4bb8454fe7eb" +dependencies = [ + "alloy-primitives", + "num_enum", + "strum 0.27.2", +] + +[[package]] +name = "alloy-consensus" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e88e1edea70787c33e11197d3f32ae380f3db19e6e061e539a5bcf8184a6b326" +dependencies = [ + "alloy-eips", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "alloy-trie", + "auto_impl", + "c-kzg", + "derive_more 1.0.0", + "k256", + "serde", +] + +[[package]] +name = "alloy-consensus-any" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57b1bb53f40c0273cd1975573cd457b39213e68584e36d1401d25fd0398a1d65" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "serde", +] + +[[package]] +name = "alloy-contract" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b668c78c4b1f12f474ede5a85e8ce550d0aa1ef7d49fd1d22855a43b960e725" +dependencies = [ + "alloy-dyn-abi", + "alloy-json-abi", + "alloy-network", + "alloy-network-primitives", + "alloy-primitives", + "alloy-provider", + "alloy-pubsub", + "alloy-rpc-types-eth", + "alloy-sol-types", + "alloy-transport", + "futures", + "futures-util", + "thiserror 2.0.18", +] + +[[package]] +name = "alloy-core" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f1ab91967646311bb7dd32db4fee380c69fe624319dcd176b89fb2a420c6b5" +dependencies = [ + "alloy-dyn-abi", + "alloy-json-abi", + "alloy-primitives", + "alloy-rlp", + "alloy-sol-types", +] + +[[package]] +name = "alloy-dyn-abi" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf69d3061e2e908a4370bda5d8d6529d5080232776975489eec0b49ce971027e" +dependencies = [ + "alloy-json-abi", + "alloy-primitives", + "alloy-sol-type-parser", + "alloy-sol-types", + "const-hex", + "itoa", + "serde", + "serde_json", + "winnow", +] + +[[package]] +name = "alloy-eip2930" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0069cf0642457f87a01a014f6dc29d5d893cd4fd8fddf0c3cdfad1bb3ebafc41" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "serde", +] + +[[package]] +name = "alloy-eip7702" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c986539255fb839d1533c128e190e557e52ff652c9ef62939e233a81dd93f7e" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "derive_more 1.0.0", + "k256", + "serde", +] + +[[package]] +name = "alloy-eips" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f9fadfe089e9ccc0650473f2d4ef0a28bc015bbca5631d9f0f09e49b557fdb3" +dependencies = [ + "alloy-eip2930", + "alloy-eip7702", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "c-kzg", + "derive_more 1.0.0", + "once_cell", + "serde", + "sha2", +] + +[[package]] +name = "alloy-genesis" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b2a4cf7b70f3495788e74ce1c765260ffe38820a2a774ff4aacb62e31ea73f9" +dependencies = [ + "alloy-primitives", + "alloy-serde", + "alloy-trie", + "serde", +] + +[[package]] +name = "alloy-json-abi" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4584e3641181ff073e9d5bec5b3b8f78f9749d9fb108a1cfbc4399a4a139c72a" +dependencies = [ + "alloy-primitives", + "alloy-sol-type-parser", + "serde", + "serde_json", +] + +[[package]] +name = "alloy-json-rpc" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e29040b9d5fe2fb70415531882685b64f8efd08dfbd6cc907120650504821105" +dependencies = [ + "alloy-primitives", + "alloy-sol-types", + "serde", + "serde_json", + "thiserror 2.0.18", + "tracing", +] + +[[package]] +name = "alloy-network" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "510cc00b318db0dfccfdd2d032411cfae64fc144aef9679409e014145d3dacc4" +dependencies = [ + "alloy-consensus", + "alloy-consensus-any", + "alloy-eips", + "alloy-json-rpc", + "alloy-network-primitives", + "alloy-primitives", + "alloy-rpc-types-any", + "alloy-rpc-types-eth", + "alloy-serde", + "alloy-signer", + "alloy-sol-types", + "async-trait", + "auto_impl", + "futures-utils-wasm", + "serde", + "serde_json", + "thiserror 2.0.18", +] + +[[package]] +name = "alloy-network-primitives" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9081c099e798b8a2bba2145eb82a9a146f01fc7a35e9ab6e7b43305051f97550" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "alloy-serde", + "serde", +] + +[[package]] +name = "alloy-primitives" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "777d58b30eb9a4db0e5f59bc30e8c2caef877fee7dc8734cf242a51a60f22e05" +dependencies = [ + "alloy-rlp", + "bytes", + "cfg-if", + "const-hex", + "derive_more 2.1.1", + "foldhash", + "hashbrown 0.15.5", + "indexmap 2.13.0", + "itoa", + "k256", + "keccak-asm", + "paste", + "proptest", + "rand 0.8.5", + "ruint", + "rustc-hash", + "serde", + "sha3", + "tiny-keccak", +] + +[[package]] +name = "alloy-provider" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc2dfaddd9a30aa870a78a4e1316e3e115ec1e12e552cbc881310456b85c1f24" +dependencies = [ + "alloy-chains", + "alloy-consensus", + "alloy-eips", + "alloy-json-rpc", + "alloy-network", + "alloy-network-primitives", + "alloy-primitives", + "alloy-pubsub", + "alloy-rpc-client", + "alloy-rpc-types-eth", + "alloy-transport", + "alloy-transport-http", + "alloy-transport-ipc", + "alloy-transport-ws", + "async-stream", + "async-trait", + "auto_impl", + "dashmap", + "futures", + "futures-utils-wasm", + "lru", + "parking_lot", + "pin-project", + "reqwest", + "schnellru", + "serde", + "serde_json", + "thiserror 2.0.18", + "tokio", + "tracing", + "url", + "wasmtimer", +] + +[[package]] +name = "alloy-pubsub" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "695809e743628d54510c294ad17a4645bd9f465aeb0d20ee9ce9877c9712dc9c" +dependencies = [ + "alloy-json-rpc", + "alloy-primitives", + "alloy-transport", + "bimap", + "futures", + "serde", + "serde_json", + "tokio", + "tokio-stream", + "tower", + "tracing", +] + +[[package]] +name = "alloy-rlp" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f70d83b765fdc080dbcd4f4db70d8d23fe4761f2f02ebfa9146b833900634b4" +dependencies = [ + "alloy-rlp-derive", + "arrayvec", + "bytes", +] + +[[package]] +name = "alloy-rlp-derive" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64b728d511962dda67c1bc7ea7c03736ec275ed2cf4c35d9585298ac9ccf3b73" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "alloy-rpc-client" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "531137b283547d5b9a5cafc96b006c64ef76810c681d606f28be9781955293b6" +dependencies = [ + "alloy-json-rpc", + "alloy-primitives", + "alloy-pubsub", + "alloy-transport", + "alloy-transport-http", + "alloy-transport-ipc", + "alloy-transport-ws", + "futures", + "pin-project", + "reqwest", + "serde", + "serde_json", + "tokio", + "tokio-stream", + "tower", + "tracing", + "url", + "wasmtimer", +] + +[[package]] +name = "alloy-rpc-types" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3410a472ce26c457e9780f708ee6bd540b30f88f1f31fdab7a11d00bd6aa1aee" +dependencies = [ + "alloy-primitives", + "alloy-rpc-types-engine", + "alloy-rpc-types-eth", + "alloy-serde", + "serde", +] + +[[package]] +name = "alloy-rpc-types-any" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed98e1af55a7d856bfa385f30f63d8d56be2513593655c904a8f4a7ec963aa3e" +dependencies = [ + "alloy-consensus-any", + "alloy-rpc-types-eth", + "alloy-serde", +] + +[[package]] +name = "alloy-rpc-types-engine" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03bd16fa4959255ebf4a7702df08f325e5631df5cdca07c8a8e58bdc10fe02e3" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "derive_more 1.0.0", + "serde", + "strum 0.26.3", +] + +[[package]] +name = "alloy-rpc-types-eth" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8737d7a6e37ca7bba9c23e9495c6534caec6760eb24abc9d5ffbaaba147818e1" +dependencies = [ + "alloy-consensus", + "alloy-consensus-any", + "alloy-eips", + "alloy-network-primitives", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "alloy-sol-types", + "derive_more 1.0.0", + "itertools 0.13.0", + "serde", + "serde_json", +] + +[[package]] +name = "alloy-serde" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5851bf8d5ad33014bd0c45153c603303e730acc8a209450a7ae6b4a12c2789e2" +dependencies = [ + "alloy-primitives", + "serde", + "serde_json", +] + +[[package]] +name = "alloy-signer" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e10ca565da6500cca015ba35ee424d59798f2e1b85bc0dd8f81dafd401f029a" +dependencies = [ + "alloy-primitives", + "async-trait", + "auto_impl", + "elliptic-curve", + "k256", + "thiserror 2.0.18", +] + +[[package]] +name = "alloy-signer-local" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47fababf5a745133490cde927d48e50267f97d3d1209b9fc9f1d1d666964d172" +dependencies = [ + "alloy-consensus", + "alloy-network", + "alloy-primitives", + "alloy-signer", + "async-trait", + "k256", + "rand 0.8.5", + "thiserror 2.0.18", +] + +[[package]] +name = "alloy-sol-macro" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e68b32b6fa0d09bb74b4cefe35ccc8269d711c26629bc7cd98a47eeb12fe353f" +dependencies = [ + "alloy-sol-macro-expander", + "alloy-sol-macro-input", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "alloy-sol-macro-expander" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2afe6879ac373e58fd53581636f2cce843998ae0b058ebe1e4f649195e2bd23c" +dependencies = [ + "alloy-json-abi", + "alloy-sol-macro-input", + "const-hex", + "heck", + "indexmap 2.13.0", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.114", + "syn-solidity", + "tiny-keccak", +] + +[[package]] +name = "alloy-sol-macro-input" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3ba01aee235a8c699d07e5be97ba215607564e71be72f433665329bec307d28" +dependencies = [ + "alloy-json-abi", + "const-hex", + "dunce", + "heck", + "macro-string", + "proc-macro2", + "quote", + "serde_json", + "syn 2.0.114", + "syn-solidity", +] + +[[package]] +name = "alloy-sol-type-parser" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c13fc168b97411e04465f03e632f31ef94cad1c7c8951bf799237fd7870d535" +dependencies = [ + "serde", + "winnow", +] + +[[package]] +name = "alloy-sol-types" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e960c4b52508ef2ae1e37cae5058e905e9ae099b107900067a503f8c454036f" +dependencies = [ + "alloy-json-abi", + "alloy-primitives", + "alloy-sol-macro", + "const-hex", + "serde", +] + +[[package]] +name = "alloy-transport" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "538a04a37221469cac0ce231b737fd174de2fdfcdd843bdd068cb39ed3e066ad" +dependencies = [ + "alloy-json-rpc", + "base64", + "futures-util", + "futures-utils-wasm", + "serde", + "serde_json", + "thiserror 2.0.18", + "tokio", + "tower", + "tracing", + "url", + "wasmtimer", +] + +[[package]] +name = "alloy-transport-http" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ed40eb1e1265b2911512f6aa1dcece9702d078f5a646730c45e39e2be00ac1c" +dependencies = [ + "alloy-json-rpc", + "alloy-transport", + "reqwest", + "serde_json", + "tower", + "tracing", + "url", +] + +[[package]] +name = "alloy-transport-ipc" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a172a59d24706b26a79a837f86d51745cb26ca6f8524712acd0208a14cff95" +dependencies = [ + "alloy-json-rpc", + "alloy-pubsub", + "alloy-transport", + "bytes", + "futures", + "interprocess", + "pin-project", + "serde_json", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "alloy-transport-ws" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fba0e39d181d13c266dbb8ca54ed584a2c66d6e9279afca89c7a6b1825e98abb" +dependencies = [ + "alloy-pubsub", + "alloy-transport", + "futures", + "http", + "rustls", + "serde_json", + "tokio", + "tokio-tungstenite", + "tracing", + "ws_stream_wasm", +] + +[[package]] +name = "alloy-trie" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d95a94854e420f07e962f7807485856cde359ab99ab6413883e15235ad996e8b" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "arrayvec", + "derive_more 1.0.0", + "nybbles", + "serde", + "smallvec", + "tracing", +] + [[package]] name = "android_system_properties" version = "0.1.5" @@ -105,9 +740,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d69eab57e8d2663efa5c63135b2af4f396d66424f88954c21104125ab6b3e6bc" dependencies = [ "ark-ec", - "ark-ff", + "ark-ff 0.5.0", "ark-r1cs-std", - "ark-std", + "ark-std 0.5.0", ] [[package]] @@ -119,14 +754,14 @@ dependencies = [ "ahash", "ark-crypto-primitives-macros", "ark-ec", - "ark-ff", + "ark-ff 0.5.0", "ark-relations", - "ark-serialize", + "ark-serialize 0.5.0", "ark-snark", - "ark-std", + "ark-std 0.5.0", "blake2", "derivative", - "digest", + "digest 0.10.7", "fnv", "merlin", "sha2", @@ -150,10 +785,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43d68f2d516162846c1238e755a7c4d131b892b70cc70c471a8e3ca3ed818fce" dependencies = [ "ahash", - "ark-ff", + "ark-ff 0.5.0", "ark-poly", - "ark-serialize", - "ark-std", + "ark-serialize 0.5.0", + "ark-std 0.5.0", "educe", "fnv", "hashbrown 0.15.5", @@ -164,18 +799,56 @@ dependencies = [ "zeroize", ] +[[package]] +name = "ark-ff" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b3235cc41ee7a12aaaf2c575a2ad7b46713a8a50bda2fc3b003a04845c05dd6" +dependencies = [ + "ark-ff-asm 0.3.0", + "ark-ff-macros 0.3.0", + "ark-serialize 0.3.0", + "ark-std 0.3.0", + "derivative", + "num-bigint 0.4.6", + "num-traits", + "paste", + "rustc_version 0.3.3", + "zeroize", +] + +[[package]] +name = "ark-ff" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba" +dependencies = [ + "ark-ff-asm 0.4.2", + "ark-ff-macros 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", + "derivative", + "digest 0.10.7", + "itertools 0.10.5", + "num-bigint 0.4.6", + "num-traits", + "paste", + "rustc_version 0.4.1", + "zeroize", +] + [[package]] name = "ark-ff" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a177aba0ed1e0fbb62aa9f6d0502e9b46dad8c2eab04c14258a1212d2557ea70" dependencies = [ - "ark-ff-asm", - "ark-ff-macros", - "ark-serialize", - "ark-std", + "ark-ff-asm 0.5.0", + "ark-ff-macros 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", "arrayvec", - "digest", + "digest 0.10.7", "educe", "itertools 0.13.0", "num-bigint 0.4.6", @@ -184,6 +857,26 @@ dependencies = [ "zeroize", ] +[[package]] +name = "ark-ff-asm" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db02d390bf6643fb404d3d22d31aee1c4bc4459600aef9113833d17e786c6e44" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-asm" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348" +dependencies = [ + "quote", + "syn 1.0.109", +] + [[package]] name = "ark-ff-asm" version = "0.5.0" @@ -194,6 +887,31 @@ dependencies = [ "syn 2.0.114", ] +[[package]] +name = "ark-ff-macros" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fd794a08ccb318058009eefdf15bcaaaaf6f8161eb3345f907222bac38b20" +dependencies = [ + "num-bigint 0.4.6", + "num-traits", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-macros" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565" +dependencies = [ + "num-bigint 0.4.6", + "num-traits", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "ark-ff-macros" version = "0.5.0" @@ -215,11 +933,11 @@ checksum = "88f1d0f3a534bb54188b8dcc104307db6c56cdae574ddc3212aec0625740fc7e" dependencies = [ "ark-crypto-primitives", "ark-ec", - "ark-ff", + "ark-ff 0.5.0", "ark-poly", "ark-relations", - "ark-serialize", - "ark-std", + "ark-serialize 0.5.0", + "ark-std 0.5.0", ] [[package]] @@ -229,9 +947,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "579305839da207f02b89cd1679e50e67b4331e2f9294a57693e5051b7703fe27" dependencies = [ "ahash", - "ark-ff", - "ark-serialize", - "ark-std", + "ark-ff 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", "educe", "fnv", "hashbrown 0.15.5", @@ -244,9 +962,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "941551ef1df4c7a401de7068758db6503598e6f01850bdb2cfdb614a1f9dbea1" dependencies = [ "ark-ec", - "ark-ff", + "ark-ff 0.5.0", "ark-relations", - "ark-std", + "ark-std 0.5.0", "educe", "num-bigint 0.4.6", "num-integer", @@ -260,12 +978,33 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec46ddc93e7af44bcab5230937635b06fb5744464dd6a7e7b083e80ebd274384" dependencies = [ - "ark-ff", - "ark-std", + "ark-ff 0.5.0", + "ark-std 0.5.0", "tracing", "tracing-subscriber", ] +[[package]] +name = "ark-serialize" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6c2b318ee6e10f8c2853e73a83adc0ccb88995aa978d8a3408d492ab2ee671" +dependencies = [ + "ark-std 0.3.0", + "digest 0.9.0", +] + +[[package]] +name = "ark-serialize" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" +dependencies = [ + "ark-std 0.4.0", + "digest 0.10.7", + "num-bigint 0.4.6", +] + [[package]] name = "ark-serialize" version = "0.5.0" @@ -273,9 +1012,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f4d068aaf107ebcd7dfb52bc748f8030e0fc930ac8e360146ca54c1203088f7" dependencies = [ "ark-serialize-derive", - "ark-std", + "ark-std 0.5.0", "arrayvec", - "digest", + "digest 0.10.7", "num-bigint 0.4.6", ] @@ -296,10 +1035,30 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d368e2848c2d4c129ce7679a7d0d2d612b6a274d3ea6a13bad4445d61b381b88" dependencies = [ - "ark-ff", + "ark-ff 0.5.0", "ark-relations", - "ark-serialize", - "ark-std", + "ark-serialize 0.5.0", + "ark-std 0.5.0", +] + +[[package]] +name = "ark-std" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df2c09229cbc5a028b1d70e00fdb2acee28b1055dfb5ca73eea49c5a25c4e7c" +dependencies = [ + "num-traits", + "rand 0.8.5", +] + +[[package]] +name = "ark-std" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" +dependencies = [ + "num-traits", + "rand 0.8.5", ] [[package]] @@ -313,16 +1072,63 @@ dependencies = [ ] [[package]] -name = "arraydeque" -version = "0.5.1" +name = "arraydeque" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +dependencies = [ + "serde", +] + +[[package]] +name = "async-stream" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "async-trait" +version = "0.1.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] [[package]] -name = "arrayvec" -version = "0.7.6" +name = "async_io_stream" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +checksum = "b6d7b9decdf35d8908a7e3ef02f64c5e9b1695e230154c0e8de3969142d9b94c" +dependencies = [ + "futures", + "pharos", + "rustc_version 0.4.1", +] [[package]] name = "atomic-polyfill" @@ -339,6 +1145,17 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" +[[package]] +name = "auto_impl" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdcb70bdbc4d478427380519163274ac86e52916e10f0a8889adf0f96d3fee7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + [[package]] name = "autocfg" version = "1.5.0" @@ -360,6 +1177,12 @@ dependencies = [ "windows-link", ] +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + [[package]] name = "base64" version = "0.22.1" @@ -372,6 +1195,12 @@ version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" +[[package]] +name = "bimap" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "230c5f1ca6a325a32553f8640d31ac9b49f2411e901e427570154868b46da4f7" + [[package]] name = "bincode" version = "1.3.3" @@ -381,6 +1210,15 @@ dependencies = [ "serde", ] +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + [[package]] name = "bit-vec" version = "0.8.0" @@ -417,7 +1255,7 @@ version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" dependencies = [ - "digest", + "digest 0.10.7", ] [[package]] @@ -435,6 +1273,18 @@ dependencies = [ "generic-array", ] +[[package]] +name = "blst" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcdb4c7013139a150f9fc55d123186dbfaba0d912817466282c73ac49e71fb45" +dependencies = [ + "cc", + "glob", + "threadpool", + "zeroize", +] + [[package]] name = "borsh" version = "1.6.0" @@ -464,6 +1314,12 @@ version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" +[[package]] +name = "byte-slice-cast" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7575182f7272186991736b70173b0ea045398f984bf5ebbb3804736ce1330c9d" + [[package]] name = "bytemuck" version = "1.24.0" @@ -499,6 +1355,21 @@ dependencies = [ "serde", ] +[[package]] +name = "c-kzg" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0307f72feab3300336fb803a57134159f6e20139af1357f36c54cb90d8e8928" +dependencies = [ + "blst", + "cc", + "glob", + "hex", + "libc", + "once_cell", + "serde", +] + [[package]] name = "camino" version = "1.2.2" @@ -525,7 +1396,7 @@ checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba" dependencies = [ "camino", "cargo-platform", - "semver", + "semver 1.0.27", "serde", "serde_json", "thiserror 2.0.18", @@ -576,12 +1447,44 @@ dependencies = [ "thiserror 2.0.18", ] +[[package]] +name = "const-hex" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bb320cac8a0750d7f25280aa97b09c26edfe161164238ecbbb31092b079e735" +dependencies = [ + "cfg-if", + "cpufeatures", + "proptest", + "serde_core", +] + [[package]] name = "const-oid" version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +[[package]] +name = "const_format" +version = "0.2.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7faa7469a93a566e9ccc1c73fe783b4a65c274c5ace346038dca9c39fe0030ad" +dependencies = [ + "const_format_proc_macros", +] + +[[package]] +name = "const_format_proc_macros" +version = "0.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + [[package]] name = "constraints" version = "0.1.0" @@ -589,6 +1492,15 @@ dependencies = [ "kernel-core", ] +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -674,6 +1586,24 @@ version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + [[package]] name = "crypto-common" version = "0.1.7" @@ -754,6 +1684,26 @@ dependencies = [ "syn 2.0.114", ] +[[package]] +name = "dashmap" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + +[[package]] +name = "data-encoding" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" + [[package]] name = "der" version = "0.7.10" @@ -828,13 +1778,34 @@ dependencies = [ "syn 2.0.114", ] +[[package]] +name = "derive_more" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" +dependencies = [ + "derive_more-impl 1.0.0", +] + [[package]] name = "derive_more" version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" dependencies = [ - "derive_more-impl", + "derive_more-impl 2.1.1", +] + +[[package]] +name = "derive_more-impl" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", + "unicode-xid", ] [[package]] @@ -843,13 +1814,23 @@ version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" dependencies = [ + "convert_case", "proc-macro2", "quote", - "rustc_version", + "rustc_version 0.4.1", "syn 2.0.114", "unicode-xid", ] +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + [[package]] name = "digest" version = "0.10.7" @@ -909,6 +1890,18 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf673e0848ef09fa4aeeba78e681cf651c0c7d35f76ee38cec8e55bc32fa111" +[[package]] +name = "doctest-file" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aac81fa3e28d21450aa4d2ac065992ba96a1d7303efbce51a95f4fd175b67562" + +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + [[package]] name = "downcast-rs" version = "1.2.1" @@ -921,7 +1914,7 @@ version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ac1e888d6830712d565b2f3a974be3200be9296bc1b03db8251a4cbf18a4a34" dependencies = [ - "digest", + "digest 0.10.7", "futures", "rand 0.8.5", "reqwest", @@ -929,6 +1922,12 @@ dependencies = [ "tokio", ] +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + [[package]] name = "dyn-clone" version = "1.0.20" @@ -939,13 +1938,30 @@ checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" name = "e2e-tests" version = "0.1.0" dependencies = [ + "alloy", "constraints", - "example-agent", + "dotenvy", + "example-yield-agent", "hex", "kernel-core", "kernel-sdk", - "methods", + "risc0-methods", "risc0-zkvm", + "tokio", +] + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest 0.10.7", + "elliptic-curve", + "rfc6979", + "signature", + "spki", ] [[package]] @@ -972,6 +1988,25 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4445909572dbd556c457c849c4ca58623d84b27c8fff1e74b0b4227d8b90d17b" +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest 0.10.7", + "ff", + "generic-array", + "group", + "pkcs8", + "rand_core 0.6.4", + "sec1", + "subtle", + "zeroize", +] + [[package]] name = "embedded-io" version = "0.4.0" @@ -1061,7 +2096,7 @@ dependencies = [ ] [[package]] -name = "example-agent" +name = "example-yield-agent" version = "0.1.0" dependencies = [ "kernel-sdk", @@ -1080,6 +2115,28 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "fastrlp" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139834ddba373bbdd213dffe02c8d110508dcf1726c2be27e8d1f7d7e1856418" +dependencies = [ + "arrayvec", + "auto_impl", + "bytes", +] + +[[package]] +name = "fastrlp" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce8dba4714ef14b8274c371879b175aa55b16b30f269663f19d576f380018dc4" +dependencies = [ + "arrayvec", + "auto_impl", + "bytes", +] + [[package]] name = "ff" version = "0.13.1" @@ -1114,6 +2171,18 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db" +[[package]] +name = "fixed-hash" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" +dependencies = [ + "byteorder", + "rand 0.8.5", + "rustc-hex", + "static_assertions", +] + [[package]] name = "flate2" version = "1.1.8" @@ -1136,6 +2205,15 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared 0.1.1", +] + [[package]] name = "foreign-types" version = "0.5.0" @@ -1143,7 +2221,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" dependencies = [ "foreign-types-macros", - "foreign-types-shared", + "foreign-types-shared 0.3.1", ] [[package]] @@ -1157,6 +2235,12 @@ dependencies = [ "syn 2.0.114", ] +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "foreign-types-shared" version = "0.3.1" @@ -1267,6 +2351,12 @@ dependencies = [ "slab", ] +[[package]] +name = "futures-utils-wasm" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42012b0f064e01aa58b545fe3727f90f7dd4020f4a3ea735b50344965f5a57e9" + [[package]] name = "gdbstub" version = "0.7.8" @@ -1299,6 +2389,7 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] @@ -1351,6 +2442,17 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "hash32" version = "0.2.1" @@ -1366,6 +2468,12 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" + [[package]] name = "hashbrown" version = "0.14.5" @@ -1382,7 +2490,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ "allocator-api2", + "equivalent", "foldhash", + "serde", ] [[package]] @@ -1408,7 +2518,7 @@ checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f" dependencies = [ "atomic-polyfill", "hash32", - "rustc_version", + "rustc_version 0.4.1", "serde", "spin", "stable_deref_trait", @@ -1420,6 +2530,12 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + [[package]] name = "hex" version = "0.4.3" @@ -1433,16 +2549,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" [[package]] -name = "host-tests" -version = "0.1.0" +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "constraints", - "example-agent", - "kernel-core", - "kernel-guest", - "kernel-sdk", - "serde", - "serde_json", + "digest 0.10.7", ] [[package]] @@ -1509,17 +2621,33 @@ dependencies = [ name = "hyper-rustls" version = "0.27.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", + "webpki-roots 1.0.5", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ - "http", + "bytes", + "http-body-util", "hyper", "hyper-util", - "rustls", - "rustls-pki-types", + "native-tls", "tokio", - "tokio-rustls", + "tokio-native-tls", "tower-service", - "webpki-roots", ] [[package]] @@ -1678,6 +2806,26 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "impl-codec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + [[package]] name = "include_bytes_aligned" version = "0.1.4" @@ -1707,6 +2855,21 @@ dependencies = [ "serde_core", ] +[[package]] +name = "interprocess" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d941b405bd2322993887859a8ee6ac9134945a24ec5ec763a8a962fc64dfec2d" +dependencies = [ + "doctest-file", + "futures-core", + "libc", + "recvmsg", + "tokio", + "widestring", + "windows-sys 0.52.0", +] + [[package]] name = "inventory" version = "0.3.21" @@ -1794,6 +2957,19 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "k256" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "once_cell", + "sha2", +] + [[package]] name = "keccak" version = "0.1.5" @@ -1803,6 +2979,16 @@ dependencies = [ "cpufeatures", ] +[[package]] +name = "keccak-asm" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "505d1856a39b200489082f90d897c3f07c455563880bc5952e38eabf731c83b6" +dependencies = [ + "digest 0.10.7", + "sha3-asm", +] + [[package]] name = "kernel-core" version = "0.1.0" @@ -1815,12 +3001,36 @@ name = "kernel-guest" version = "0.1.0" dependencies = [ "constraints", - "example-agent", "kernel-core", "kernel-sdk", "risc0-zkvm", ] +[[package]] +name = "kernel-guest-binding-yield" +version = "0.1.0" +dependencies = [ + "constraints", + "example-yield-agent", + "kernel-core", + "kernel-guest", + "kernel-sdk", +] + +[[package]] +name = "kernel-host-tests" +version = "0.1.0" +dependencies = [ + "constraints", + "example-yield-agent", + "kernel-core", + "kernel-guest", + "kernel-guest-binding-yield", + "kernel-sdk", + "serde", + "serde_json", +] + [[package]] name = "kernel-sdk" version = "0.1.0" @@ -1929,12 +3139,32 @@ version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +[[package]] +name = "lru" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" +dependencies = [ + "hashbrown 0.15.5", +] + [[package]] name = "lru-slab" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" +[[package]] +name = "macro-string" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b27834086c65ec3f9387b096d66e99f221cf081c2b738042aa252bcd41204e3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + [[package]] name = "malachite" version = "0.4.22" @@ -2054,19 +3284,12 @@ dependencies = [ "bitflags 2.10.0", "block", "core-graphics-types", - "foreign-types", + "foreign-types 0.5.0", "log", "objc", "paste", ] -[[package]] -name = "methods" -version = "0.1.0" -dependencies = [ - "risc0-build", -] - [[package]] name = "miniz_oxide" version = "0.8.9" @@ -2088,6 +3311,23 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "native-tls" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "ndarray" version = "0.16.1" @@ -2203,6 +3443,16 @@ dependencies = [ "libm", ] +[[package]] +name = "num_cpus" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" +dependencies = [ + "hermit-abi", + "libc", +] + [[package]] name = "num_enum" version = "0.7.5" @@ -2233,6 +3483,19 @@ dependencies = [ "cc", ] +[[package]] +name = "nybbles" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8983bb634df7248924ee0c4c3a749609b5abcb082c28fffe3254b3eb3602b307" +dependencies = [ + "alloy-rlp", + "const-hex", + "proptest", + "serde", + "smallvec", +] + [[package]] name = "objc" version = "0.2.7" @@ -2268,12 +3531,84 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "openssl" +version = "0.10.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" +dependencies = [ + "bitflags 2.10.0", + "cfg-if", + "foreign-types 0.3.2", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + +[[package]] +name = "openssl-sys" +version = "0.9.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "option-ext" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" +[[package]] +name = "parity-scale-codec" +version = "3.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799781ae679d79a948e13d4824a40970bfa500058d245760dd857301059810fa" +dependencies = [ + "arrayvec", + "bitvec", + "byte-slice-cast", + "const_format", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "rustversion", + "serde", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34b4653168b563151153c9e4c08ebed57fb8262bebfa79711552fa983c623e7a" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.114", +] + [[package]] name = "parking_lot" version = "0.12.5" @@ -2318,6 +3653,46 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" +[[package]] +name = "pest" +version = "2.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9eb05c21a464ea704b53158d358a31e6425db2f63a1a7312268b05fe2b75f7" +dependencies = [ + "memchr", + "ucd-trie", +] + +[[package]] +name = "pharos" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9567389417feee6ce15dd6527a8a1ecac205ef62c2932bcf3d9f6fc5b78b414" +dependencies = [ + "futures", + "rustc_version 0.4.1", +] + +[[package]] +name = "pin-project" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + [[package]] name = "pin-project-lite" version = "0.2.16" @@ -2409,6 +3784,17 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "primitive-types" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2" +dependencies = [ + "fixed-hash", + "impl-codec", + "uint", +] + [[package]] name = "proc-macro-crate" version = "3.4.0" @@ -2418,6 +3804,28 @@ dependencies = [ "toml_edit 0.23.10+spec-1.0.0", ] +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn 2.0.114", +] + [[package]] name = "proc-macro2" version = "1.0.106" @@ -2433,11 +3841,16 @@ version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bee689443a2bd0a16ab0348b52ee43e3b2d1b1f931c8aa5c9f8de4c86fbe8c40" dependencies = [ + "bit-set", + "bit-vec", "bitflags 2.10.0", "num-traits", "rand 0.9.2", "rand_chacha 0.9.0", "rand_xorshift", + "regex-syntax", + "rusty-fork", + "tempfile", "unarray", ] @@ -2478,6 +3891,12 @@ dependencies = [ "parking_lot", ] +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + [[package]] name = "quinn" version = "0.11.9" @@ -2563,6 +3982,7 @@ dependencies = [ "libc", "rand_chacha 0.3.1", "rand_core 0.6.4", + "serde", ] [[package]] @@ -2648,6 +4068,12 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "recvmsg" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3edd4d5d42c92f0a659926464d4cce56b562761267ecf0f469d85b7de384175" + [[package]] name = "redox_syscall" version = "0.5.18" @@ -2731,9 +4157,11 @@ dependencies = [ "http-body-util", "hyper", "hyper-rustls", + "hyper-tls", "hyper-util", "js-sys", "log", + "native-tls", "percent-encoding", "pin-project-lite", "quinn", @@ -2744,6 +4172,7 @@ dependencies = [ "serde_urlencoded", "sync_wrapper", "tokio", + "tokio-native-tls", "tokio-rustls", "tower", "tower-http", @@ -2752,7 +4181,17 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "webpki-roots", + "webpki-roots 1.0.5", +] + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", ] [[package]] @@ -2784,7 +4223,7 @@ dependencies = [ "anyhow", "borsh", "bytemuck", - "derive_more", + "derive_more 2.1.1", "elf", "lazy_static", "postcard", @@ -2792,7 +4231,7 @@ dependencies = [ "risc0-zkp", "risc0-zkvm-platform", "ruint", - "semver", + "semver 1.0.27", "serde", "tracing", ] @@ -2814,7 +4253,7 @@ dependencies = [ "risc0-zkp", "risc0-zkvm-platform", "rzup", - "semver", + "semver 1.0.27", "serde", "serde_json", "stability", @@ -2864,7 +4303,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30a8f21cc053fe9892acebbe0ebe2610a5d79ad638cd17f2e5122cf0b3e7cd1a" dependencies = [ "cc", - "derive_more", + "derive_more 2.1.1", "glob", "risc0-build-kernel", "risc0-core", @@ -2919,7 +4358,7 @@ dependencies = [ "bytemuck", "byteorder", "cfg-if", - "derive_more", + "derive_more 2.1.1", "enum-map", "gdbstub", "gdbstub_arch", @@ -2949,7 +4388,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb25f3935e53e89ca020224ad0c09de96cab89a215054c0cee290405074a5166" dependencies = [ "cc", - "derive_more", + "derive_more 2.1.1", "glob", "risc0-build-kernel", "risc0-core", @@ -2977,9 +4416,9 @@ dependencies = [ "anyhow", "ark-bn254", "ark-ec", - "ark-ff", + "ark-ff 0.5.0", "ark-groth16", - "ark-serialize", + "ark-serialize 0.5.0", "bytemuck", "cfg-if", "hex", @@ -2995,6 +4434,13 @@ dependencies = [ "tracing", ] +[[package]] +name = "risc0-methods" +version = "0.1.0" +dependencies = [ + "risc0-build", +] + [[package]] name = "risc0-sys" version = "1.5.0" @@ -3027,7 +4473,7 @@ dependencies = [ "borsh", "bytemuck", "cfg-if", - "digest", + "digest 0.10.7", "ff", "hex", "hex-literal", @@ -3059,7 +4505,7 @@ dependencies = [ "borsh", "bytemuck", "bytes", - "derive_more", + "derive_more 2.1.1", "elf", "enum-map", "gdbstub", @@ -3087,7 +4533,7 @@ dependencies = [ "rrs-lib", "rustc-demangle", "rzup", - "semver", + "semver 1.0.27", "serde", "sha2", "stability", @@ -3112,6 +4558,16 @@ dependencies = [ "stability", ] +[[package]] +name = "rlp" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb919243f34364b6bd2fc10ef797edbfa75f33c252e7998527479c6d6b47e1ec" +dependencies = [ + "bytes", + "rustc-hex", +] + [[package]] name = "rrs-lib" version = "0.1.0" @@ -3129,7 +4585,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" dependencies = [ "const-oid", - "digest", + "digest 0.10.7", "num-bigint-dig", "num-integer", "num-traits", @@ -3148,10 +4604,23 @@ version = "1.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c141e807189ad38a07276942c6623032d3753c8859c146104ac2e4d68865945a" dependencies = [ + "alloy-rlp", + "ark-ff 0.3.0", + "ark-ff 0.4.2", + "ark-ff 0.5.0", "borsh", + "bytes", + "fastrlp 0.3.1", + "fastrlp 0.4.0", + "num-bigint 0.4.6", + "num-integer", + "num-traits", + "parity-scale-codec", + "primitive-types", "proptest", "rand 0.8.5", "rand 0.9.2", + "rlp", "ruint-macro", "serde_core", "valuable", @@ -3176,13 +4645,28 @@ version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" +[[package]] +name = "rustc-hex" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" + +[[package]] +name = "rustc_version" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" +dependencies = [ + "semver 0.11.0", +] + [[package]] name = "rustc_version" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ - "semver", + "semver 1.0.27", ] [[package]] @@ -3239,6 +4723,18 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +[[package]] +name = "rusty-fork" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc6bf79ff24e648f6da1f8d1f011e9cac26491b619e6b9280f2b47f1774e6ee2" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", +] + [[package]] name = "ruzstd" version = "0.7.3" @@ -3262,11 +4758,11 @@ checksum = "5d2aed296f203fa64bcb4b52069356dd86d6ec578593985b919b6995bee1f0ae" dependencies = [ "hex", "rsa", - "semver", + "semver 1.0.27", "serde", "serde_with", "sha2", - "strum", + "strum 0.27.2", "tempfile", "thiserror 2.0.18", "toml", @@ -3282,6 +4778,15 @@ dependencies = [ "bytemuck", ] +[[package]] +name = "schannel" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "schemars" version = "0.9.0" @@ -3306,12 +4811,69 @@ dependencies = [ "serde_json", ] +[[package]] +name = "schnellru" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "356285bbf17bea63d9e52e96bd18f039672ac92b55b8cb997d6162a2a37d1649" +dependencies = [ + "ahash", + "cfg-if", + "hashbrown 0.13.2", +] + [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.10.0", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" +dependencies = [ + "semver-parser", +] + [[package]] name = "semver" version = "1.0.27" @@ -3322,6 +4884,21 @@ dependencies = [ "serde_core", ] +[[package]] +name = "semver-parser" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9900206b54a3527fdc7b8a938bffd94a568bac4f4aa8113b209df75a09c0dec2" +dependencies = [ + "pest", +] + +[[package]] +name = "send_wrapper" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" + [[package]] name = "serde" version = "1.0.228" @@ -3417,6 +4994,17 @@ dependencies = [ "syn 2.0.114", ] +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.7", +] + [[package]] name = "sha2" version = "0.10.9" @@ -3425,7 +5013,27 @@ checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", "cpufeatures", - "digest", + "digest 0.10.7", +] + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest 0.10.7", + "keccak", +] + +[[package]] +name = "sha3-asm" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28efc5e327c837aa837c59eae585fc250715ef939ac32881bcc11677cd02d46" +dependencies = [ + "cc", + "cfg-if", ] [[package]] @@ -3434,13 +5042,23 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + [[package]] name = "signature" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ - "digest", + "digest 0.10.7", "rand_core 0.6.4", ] @@ -3461,6 +5079,9 @@ name = "smallvec" version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +dependencies = [ + "serde", +] [[package]] name = "socket2" @@ -3519,13 +5140,35 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +dependencies = [ + "strum_macros 0.26.4", +] + [[package]] name = "strum" version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" dependencies = [ - "strum_macros", + "strum_macros 0.27.2", +] + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.114", ] [[package]] @@ -3568,6 +5211,18 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn-solidity" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab4e6eed052a117409a1a744c8bda9c3ea6934597cf7419f791cb7d590871c4c" +dependencies = [ + "paste", + "proc-macro2", + "quote", + "syn 2.0.114", +] + [[package]] name = "sync_wrapper" version = "1.0.2" @@ -3647,6 +5302,15 @@ dependencies = [ "syn 2.0.114", ] +[[package]] +name = "threadpool" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" +dependencies = [ + "num_cpus", +] + [[package]] name = "time" version = "0.3.46" @@ -3678,6 +5342,15 @@ dependencies = [ "time-core", ] +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + [[package]] name = "tinystr" version = "0.8.2" @@ -3712,11 +5385,35 @@ dependencies = [ "bytes", "libc", "mio", + "parking_lot", "pin-project-lite", + "signal-hook-registry", "socket2", + "tokio-macros", "windows-sys 0.61.2", ] +[[package]] +name = "tokio-macros" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + [[package]] name = "tokio-rustls" version = "0.26.4" @@ -3727,6 +5424,47 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-stream" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", + "tokio-util", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edc5f74e248dc973e0dbb7b74c7e0d6fcc301c694ff50049504004ef4d0cdcd9" +dependencies = [ + "futures-util", + "log", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tungstenite", + "webpki-roots 0.26.11", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + [[package]] name = "toml" version = "0.8.23" @@ -3891,6 +5629,26 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "tungstenite" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18e5b8366ee7a95b16d32197d0b2604b43a0be89dc5fac9f8e96ccafbaedda8a" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "rand 0.8.5", + "rustls", + "rustls-pki-types", + "sha1", + "thiserror 1.0.69", + "utf-8", +] + [[package]] name = "twox-hash" version = "1.6.3" @@ -3943,6 +5701,24 @@ dependencies = [ "syn 2.0.114", ] +[[package]] +name = "ucd-trie" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + +[[package]] +name = "uint" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + [[package]] name = "unarray" version = "0.1.4" @@ -3955,6 +5731,12 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + [[package]] name = "unicode-xid" version = "0.2.6" @@ -3979,6 +5761,12 @@ dependencies = [ "serde", ] +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + [[package]] name = "utf8_iter" version = "1.0.4" @@ -3991,12 +5779,27 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "wait-timeout" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" +dependencies = [ + "libc", +] + [[package]] name = "want" version = "0.3.1" @@ -4080,6 +5883,20 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "wasmtimer" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c598d6b99ea013e35844697fc4670d08339d5cda15588f193c6beedd12f644b" +dependencies = [ + "futures", + "js-sys", + "parking_lot", + "pin-utils", + "slab", + "wasm-bindgen", +] + [[package]] name = "web-sys" version = "0.3.85" @@ -4100,6 +5917,15 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki-roots" +version = "0.26.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" +dependencies = [ + "webpki-roots 1.0.5", +] + [[package]] name = "webpki-roots" version = "1.0.5" @@ -4119,6 +5945,12 @@ dependencies = [ "safe_arch", ] +[[package]] +name = "widestring" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" + [[package]] name = "windows-core" version = "0.62.2" @@ -4355,6 +6187,25 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" +[[package]] +name = "ws_stream_wasm" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c173014acad22e83f16403ee360115b38846fe754e735c5d9d3803fe70c6abc" +dependencies = [ + "async_io_stream", + "futures", + "js-sys", + "log", + "pharos", + "rustc_version 0.4.1", + "send_wrapper", + "thiserror 2.0.18", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "wyz" version = "0.5.1" diff --git a/Cargo.toml b/Cargo.toml index 2471933..e399d0b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,14 +1,24 @@ [workspace] resolver = "2" members = [ - "crates/kernel-core", - "crates/kernel-guest", - "crates/constraints", - "crates/kernel-sdk", - "crates/example-agent", - "crates/host-tests", - "crates/methods", - "crates/e2e-tests", + # Protocol crates + "crates/protocol/kernel-core", + "crates/protocol/constraints", + + # SDK crates + "crates/sdk/kernel-sdk", + + # Runtime crates + "crates/runtime/kernel-guest", + "crates/runtime/risc0-methods", + + # Agent crates + "crates/agents/examples/example-yield-agent", + "crates/agents/wrappers/kernel-guest-binding-yield", + + # Testing crates + "crates/testing/kernel-host-tests", + "crates/testing/e2e-tests", ] [workspace.dependencies] diff --git a/README.md b/README.md index 5a80498..c4255de 100644 --- a/README.md +++ b/README.md @@ -12,18 +12,43 @@ This repository implements the **Canonical zkVM Guest Program**. The kernel prov ## Architecture -The project is organized as a Rust workspace with the following crates: - -| Crate | Description | -|-------|-------------| -| `kernel-core` | Core types, deterministic binary codec, and SHA-256 hashing | -| `kernel-guest` | RISC Zero guest binary implementation | -| `kernel-sdk` | Canonical agent interface and SDK | -| `constraints` | Constraint engine with action validation, asset whitelists, position limits | -| `example-agent` | Reference agent implementation with `agent_main` entrypoint | -| `methods` | RISC Zero build crate - exports `ZKVM_GUEST_ELF` and `ZKVM_GUEST_ID` | -| `e2e-tests` | End-to-end zkVM proof tests | -| `host-tests` | Unit test suite (92 tests) | +The project is organized as a Rust workspace: + +``` +crates/ +├── protocol/ # Core protocol types +│ ├── kernel-core/ # Types, deterministic codec, SHA-256 hashing +│ └── constraints/ # Constraint engine with action validation +├── sdk/ +│ └── kernel-sdk/ # Agent development SDK +├── runtime/ # zkVM execution +│ ├── kernel-guest/ # Agent-agnostic kernel execution logic +│ └── risc0-methods/ # RISC Zero build - exports ELF and IMAGE_ID +├── agents/ +│ ├── examples/ +│ │ └── example-yield-agent/ # Yield farming agent implementation +│ └── wrappers/ +│ └── kernel-guest-binding-yield/ # Binds yield agent to kernel +└── testing/ + ├── kernel-host-tests/ # Unit test suite + └── e2e-tests/ # End-to-end zkVM proof tests +``` + +### Agent-Agnostic Design + +The kernel uses trait-based dependency injection, allowing new agents without modifying kernel code: + +```rust +pub trait AgentEntrypoint { + fn code_hash(&self) -> [u8; 32]; + fn run(&self, ctx: &AgentContext, opaque_inputs: &[u8]) -> AgentOutput; +} + +// Execute kernel with any agent +let journal = kernel_main_with_agent(&input_bytes, &MyAgent)?; +``` + +Wrapper crates (e.g., `kernel-guest-binding-yield`) bind specific agents to the kernel. ## Protocol Constants @@ -39,7 +64,7 @@ The project is organized as a Rust workspace with the following crates: # Build all crates cargo build --release -# Build with zkVM features (when RISC Zero dependencies are available) +# Build with zkVM features cargo build --release --features risc0 ``` @@ -53,11 +78,6 @@ cargo test # Run tests with verbose output cargo test -- --nocapture - -# Run specific test categories -cargo test test_determinism -cargo test test_golden_vector -cargo test test_constraint ``` ### E2E zkVM Proof Tests @@ -69,40 +89,38 @@ End-to-end tests that generate actual RISC Zero proofs: cargo install cargo-risczero cargo risczero install -# Run E2E proof tests (requires RISC Zero) -cargo test -p e2e-tests --features risc0-e2e -- --test-threads=1 --nocapture +# Run E2E proof tests +cargo test -p e2e-tests --features risc0-e2e -- --nocapture ``` -**Note:** Use `--test-threads=1` to avoid parallel proof generation exhausting memory. +### On-Chain E2E Test -### Guest Program Flow +Full end-to-end test with on-chain verification on Sepolia testnet. -1. Read input from zkVM environment -2. Decode and validate `KernelInputV1` -3. Verify protocol version and agent code hash -4. Compute input commitment -5. Execute agent via `agent_main()` entrypoint -6. Enforce constraints (mandatory, unskippable) -7. Construct canonical journal (Success or Failure) -8. Commit journal or abort on hard error +See [e2e-tests/README.md](crates/testing/e2e-tests/README.md) for detailed instructions. -## On-Chain Verification +## On-Chain Deployment (Sepolia) -The E2E tests extract data needed for Solidity verifier integration: +| Contract | Address | +|----------|---------| +| KernelExecutionVerifier | `0x9Ef5bAB590AFdE8036D57b89ccD2947D4E3b1EFA` | +| KernelVault | `0xAdeDA97D2D07C7f2e332fD58F40Eb4f7F0192be7` | +| MockYieldSource | `0x7B35E3F2e810170f146d31b00262b9D7138F9b39` | +| RISC Zero Verifier Router | `0x925d8331ddc0a1F0d96E68CF073DFE1d92b69187` | -```rust -// From receipt after proof generation -seal: bytes // 256-byte Groth16 proof -journal: bytes // KernelJournalV1 (variable length) -imageId: bytes32 // ZKVM_GUEST_ID (guest identity) -``` +### Yield Agent Registration + +| Field | Value | +|-------|-------| +| IMAGE_ID | `0x5f42241afd61bf9e341442c8baffa9c544cf20253720f2540cf6705f27bae2c4` | +| AGENT_CODE_HASH | `0x5aac6b1fedf1b0c0ccc037c3223b7b5c8b679f48b9c599336c0dc777be88924b` | +| AGENT_ID | `0x0000000000000000000000000000000000000000000000000000000000000001` | ## Usage ### Quick Start ```bash -# Clone and test the implementation git clone https://github.com/Defiesta/execution-kernel.git cd execution-kernel cargo test @@ -117,34 +135,46 @@ let input = KernelInputV1 { protocol_version: PROTOCOL_VERSION, kernel_version: KERNEL_VERSION, agent_id: [0x42; 32], - agent_code_hash: example_agent::AGENT_CODE_HASH, // Must match linked agent + agent_code_hash: example_yield_agent::AGENT_CODE_HASH, constraint_set_hash: [0xbb; 32], input_root: [0xcc; 32], execution_nonce: 1, - opaque_agent_inputs: vec![1, 2, 3, 4, 5], // Max 64KB + opaque_agent_inputs: vec![/* 48 bytes for yield agent */], }; -let input_bytes = input.encode()?; // Deterministic binary encoding +let input_bytes = input.encode()?; ``` ### Running the Kernel ```rust -use kernel_guest::kernel_main; +use kernel_guest::kernel_main_with_agent; +use kernel_guest_binding_yield::YieldAgentWrapper; use kernel_core::*; -// Execute kernel with encoded input -let journal_bytes = kernel_main(&input_bytes)?; +// Execute kernel with yield agent +let journal_bytes = kernel_main_with_agent(&input_bytes, &YieldAgentWrapper)?; // Decode the resulting journal let journal = KernelJournalV1::decode(&journal_bytes)?; // Journal contains: // - input_commitment: SHA256(input_bytes) -// - action_commitment: SHA256(agent_output_bytes) or empty commitment on failure +// - action_commitment: SHA256(agent_output_bytes) // - execution_status: Success (0x01) or Failure (0x02) ``` +### Guest Program Flow + +1. Read input from zkVM environment +2. Decode and validate `KernelInputV1` +3. Verify protocol version and agent code hash +4. Compute input commitment +5. Execute agent via `AgentEntrypoint::run()` +6. Enforce constraints (mandatory, unskippable) +7. Construct canonical journal (Success or Failure) +8. Commit journal or abort on hard error + ## Consensus-Critical Properties This implementation prioritizes **determinism and correctness over convenience**: @@ -159,18 +189,6 @@ This implementation prioritizes **determinism and correctness over convenience** Any deviation from these principles breaks protocol consensus. -## Development - -### Documentation - -- `docs/P0.1_DOCUMENTATION.md` - Foundational concepts and design rationale -- `docs/P0.2_DOCUMENTATION.md` - Canonical codec specification -- `docs/P0.3_DOCUMENTATION.md` - Constraint system documentation -- `spec/codec.md` - Wire format specification -- `spec/constraints.md` - Constraint system specification -- `spec/sdk.md` - Kernel SDK specification -- `spec/e2e-tests.md` - E2E testing specification - ## Security Considerations The kernel assumes a **malicious host environment** and defends against: @@ -181,12 +199,9 @@ The kernel assumes a **malicious host environment** and defends against: - **Non-determinism exploitation** - Strict deterministic execution requirements - **Protocol version confusion** - Explicit version validation on all inputs - **Encoding malleability** - Exact payload lengths, trailing bytes rejected -- **Timestamp overflow attacks** - Checked arithmetic for cooldown calculations All security properties are enforced cryptographically through zkVM proofs. -**Note:** The `action.target` field is not validated by the constraint engine. Executor contracts are responsible for target validation. - ## License Licensed under the Apache License, Version 2.0. See [LICENSE](LICENSE) for details. diff --git a/contracts/.gitignore b/contracts/.gitignore index 3269660..bb8bc57 100644 --- a/contracts/.gitignore +++ b/contracts/.gitignore @@ -2,8 +2,10 @@ cache/ out/ +/script + # Ignores development broadcast logs -!/broadcast +/broadcast /broadcast/*/31337/ /broadcast/**/dry-run/ diff --git a/contracts/remappings.txt b/contracts/remappings.txt index 964295a..4f5adee 100644 --- a/contracts/remappings.txt +++ b/contracts/remappings.txt @@ -1,3 +1,4 @@ forge-std/=lib/forge-std/src/ risc0-ethereum/=lib/risc0-ethereum/contracts/src/ @openzeppelin/=lib/risc0-ethereum/lib/openzeppelin-contracts/ +openzeppelin/=lib/risc0-ethereum/lib/openzeppelin-contracts/ diff --git a/contracts/script/TestVerifyAndParse.s.sol b/contracts/script/TestVerifyAndParse.s.sol index e4be37a..73583f2 100644 --- a/contracts/script/TestVerifyAndParse.s.sol +++ b/contracts/script/TestVerifyAndParse.s.sol @@ -12,7 +12,7 @@ contract TestVerifyAndParse is Script { bytes32 constant IMAGE_ID = 0xb326f06dbfc60f5e72d2d7cddf94f7991cff99dfd67f69357713bb9f49c3d195; bytes32 constant AGENT_ID = 0x4242424242424242424242424242424242424242424242424242424242424242; - function run() external { + function run() external view { KernelExecutionVerifier verifier = KernelExecutionVerifier(VERIFIER_ADDRESS); // Seal from zkVM proof (with selector prefix 0x73c457ba) diff --git a/contracts/src/KernelOutputParser.sol b/contracts/src/KernelOutputParser.sol index fe17856..7659405 100644 --- a/contracts/src/KernelOutputParser.sol +++ b/contracts/src/KernelOutputParser.sol @@ -47,6 +47,9 @@ library KernelOutputParser { /// @notice Action type for ERC20 transfer uint32 public constant ACTION_TYPE_TRANSFER_ERC20 = 0x00000003; + /// @notice Action type for no-op + uint32 public constant ACTION_TYPE_NO_OP = 0x00000004; + // ============ Errors ============ /// @notice Thrown when action count exceeds maximum diff --git a/contracts/src/KernelVault.sol b/contracts/src/KernelVault.sol index 54ec90c..83bb262 100644 --- a/contracts/src/KernelVault.sol +++ b/contracts/src/KernelVault.sol @@ -24,6 +24,9 @@ contract KernelVault is ReentrancyGuard { /// @notice Action type for ERC20 transfer uint32 public constant ACTION_TYPE_TRANSFER_ERC20 = 0x00000003; + /// @notice Action type for no-op + uint32 public constant ACTION_TYPE_NO_OP = 0x00000004; + /// @notice Maximum allowed gap between nonces for liveness (prevents stuck execution) /// @dev Allows operators to skip intermediate executions if needed (e.g., if nonce N is lost/stuck, /// executions N+1 through N+MAX_NONCE_GAP can still proceed). This weakens strict ordering @@ -46,12 +49,15 @@ contract KernelVault is ReentrancyGuard { /// @notice Total shares outstanding uint256 public totalShares; - /// @notice Shares balance per account - mapping(address => uint256) public shares; + /// @notice Last execution timestamp + uint256 public lastExecutionTimestamp; /// @notice Last execution nonce processed (for replay protection) uint64 public lastExecutionNonce; + /// @notice Shares balance per account + mapping(address => uint256) public shares; + // ============ Events ============ /// @notice Emitted when tokens are deposited @@ -68,6 +74,9 @@ contract KernelVault is ReentrancyGuard { /// @notice Emitted when an action is executed event ActionExecuted(uint256 indexed actionIndex, uint32 actionType, bytes32 target, bool success); + /// @notice Emitted when a no-op action is executed + event NoOpActionExecuted(uint256 indexed actionIndex, uint32 actionType); + /// @notice Emitted when a transfer action is executed (more detailed than ActionExecuted) /// @dev For transfers, `to` is the meaningful recipient (ActionExecuted.target is the token address) event TransferExecuted(uint256 indexed actionIndex, address indexed token, address indexed to, uint256 amount); @@ -122,6 +131,15 @@ contract KernelVault is ReentrancyGuard { /// @notice Zero assets out calculated error ZeroAssetsOut(); + /// @notice ETH deposit amount doesn't match msg.value + error ETHDepositMismatch(uint256 expected, uint256 actual); + + /// @notice ETH transfer failed + error ETHTransferFailed(); + + /// @notice Wrong deposit function called for this vault type + error WrongDepositFunction(); + // ============ Constructor ============ /// @notice Initialize the vault @@ -136,17 +154,18 @@ contract KernelVault is ReentrancyGuard { // ============ Deposit/Withdraw ============ - /// @notice Deposit tokens and receive shares based on current PPS - /// @param assets Amount of tokens to deposit + /// @notice Deposit ERC20 tokens and receive shares based on current PPS + /// @param assets Amount of ERC20 tokens to deposit /// @return sharesMinted Number of shares minted based on current exchange rate /// @dev MVP uses simple PPS math. First deposit is 1:1, subsequent deposits use /// shares = assets * totalShares / totalAssets. - function deposit(uint256 assets) external nonReentrant returns (uint256 sharesMinted) { + function depositERC20Tokens(uint256 assets) external nonReentrant returns (uint256 sharesMinted) { + if (address(asset) == address(0)) revert WrongDepositFunction(); if (assets == 0) revert ZeroDeposit(); - // IMPORTANT: Calculate shares BEFORE transfer (use pre-transfer totalAssets) + // Calculate shares BEFORE transfer (use pre-transfer totalAssets) uint256 supply = totalShares; - uint256 assetsBefore = totalAssets(); + uint256 assetsBefore = asset.balanceOf(address(this)); if (supply == 0) { // First deposit: 1:1 ratio @@ -169,7 +188,38 @@ contract KernelVault is ReentrancyGuard { emit Deposit(msg.sender, assets, sharesMinted); } - /// @notice Withdraw tokens by burning shares based on current PPS + /// @notice Deposit ETH and receive shares based on current PPS + /// @return sharesMinted Number of shares minted based on current exchange rate + /// @dev MVP uses simple PPS math. First deposit is 1:1, subsequent deposits use + /// shares = msg.value * totalShares / totalAssets. + /// Only works when vault asset is address(0) (ETH vault). + function depositETH() external payable nonReentrant returns (uint256 sharesMinted) { + if (address(asset) != address(0)) revert WrongDepositFunction(); + if (msg.value == 0) revert ZeroDeposit(); + + // Calculate shares BEFORE transfer + // msg.value is already added to balance, so subtract it for pre-transfer calculation + uint256 supply = totalShares; + uint256 assetsBefore = address(this).balance - msg.value; + + if (supply == 0) { + // First deposit: 1:1 ratio + sharesMinted = msg.value; + } else { + // Subsequent deposits: standard PPS calculation + if (assetsBefore == 0) revert ZeroAssets(); + sharesMinted = (msg.value * supply) / assetsBefore; + if (sharesMinted == 0) revert ZeroShares(); + } + + // Update state + shares[msg.sender] += sharesMinted; + totalShares += sharesMinted; + + emit Deposit(msg.sender, msg.value, sharesMinted); + } + + /// @notice Withdraw tokens (or ETH if asset is address(0)) by burning shares based on current PPS /// @param shareAmount Number of shares to burn /// @return assetsOut Amount of tokens returned based on current exchange rate function withdraw(uint256 shareAmount) external nonReentrant returns (uint256 assetsOut) { @@ -190,9 +240,15 @@ contract KernelVault is ReentrancyGuard { shares[msg.sender] -= shareAmount; totalShares -= shareAmount; - // Transfer tokens - bool success = asset.transfer(msg.sender, assetsOut); - if (!success) revert TransferFailed(); + // Transfer tokens or ETH + bool isETH = address(asset) == address(0); + if (isETH) { + (bool success, ) = msg.sender.call{value: assetsOut}(""); + if (!success) revert ETHTransferFailed(); + } else { + bool success = asset.transfer(msg.sender, assetsOut); + if (!success) revert TransferFailed(); + } emit Withdraw(msg.sender, assetsOut, shareAmount); } @@ -258,16 +314,21 @@ contract KernelVault is ReentrancyGuard { /// @param index Action index (for events) /// @param action The action to execute function _executeAction(uint256 index, KernelOutputParser.Action memory action) internal { + + lastExecutionTimestamp = block.timestamp; + if (action.actionType == ACTION_TYPE_TRANSFER_ERC20) { _executeTransferERC20(index, action); } else if (action.actionType == ACTION_TYPE_CALL) { _executeCall(index, action); + } else if (action.actionType == ACTION_TYPE_NO_OP) { + emit NoOpActionExecuted(index, action.actionType); } else { revert UnknownActionType(action.actionType); } } - /// @notice Execute a TRANSFER_ERC20 action + /// @notice Execute a TRANSFER_ERC20 action (also handles ETH if token is address(0)) /// @dev Payload format: abi.encode(address token, address to, uint256 amount) /// MVP: only allows transfers of the vault's single asset function _executeTransferERC20(uint256 index, KernelOutputParser.Action memory action) internal { @@ -283,9 +344,16 @@ contract KernelVault is ReentrancyGuard { revert InvalidTransferPayload(); } - // Execute transfer - bool success = IERC20(token).transfer(to, amount); - if (!success) revert TransferFailed(); + // Execute transfer (ETH or ERC20) + if (token == address(0)) { + // ETH transfer + (bool success, ) = to.call{value: amount}(""); + if (!success) revert ETHTransferFailed(); + } else { + // ERC20 transfer + bool success = IERC20(token).transfer(to, amount); + if (!success) revert TransferFailed(); + } // Emit detailed transfer event (includes recipient `to` for better observability) emit TransferExecuted(index, token, to, amount); @@ -321,8 +389,11 @@ contract KernelVault is ReentrancyGuard { // ============ View Functions ============ /// @notice Returns total assets held by the vault - /// @return Total balance of the vault's asset + /// @return Total balance of the vault's asset (ETH balance if asset is address(0)) function totalAssets() public view returns (uint256) { + if (address(asset) == address(0)) { + return address(this).balance; + } return asset.balanceOf(address(this)); } diff --git a/contracts/src/MockYieldSource.sol b/contracts/src/MockYieldSource.sol new file mode 100644 index 0000000..1826984 --- /dev/null +++ b/contracts/src/MockYieldSource.sol @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.20; + +/// @title MockYieldSource +/// @notice Mock yield source for testing that returns 10% yield on withdrawals +/// @dev Only the designated vault can withdraw funds +contract MockYieldSource { + + // ============ State ============ + + /// @notice The vault address that can withdraw + address public immutable vault; + + /// @notice Deposited amounts per depositor + mapping(address => uint256) public deposits; + + // ============ Events ============ + + /// @notice Emitted when ETH is deposited + event Deposited(address indexed depositor, uint256 amount); + + /// @notice Emitted when ETH is withdrawn with yield + event Withdrawn(address indexed depositor, uint256 principal, uint256 yield); + + // ============ Errors ============ + + /// @notice Caller is not the vault + error OnlyVault(); + + /// @notice No deposit found for the depositor + error NoDeposit(); + + /// @notice Transfer failed + error TransferFailed(); + + // ============ Constructor ============ + + /// @notice Initialize the mock yield source + /// @param _vault The vault address that can withdraw + constructor(address _vault) { + vault = _vault; + } + + // ============ Receive ============ + + /// @notice Receive ETH and track the deposit + receive() external payable { + deposits[msg.sender] += msg.value; + emit Deposited(msg.sender, msg.value); + } + + // ============ Withdraw ============ + + /// @notice Withdraw deposited amount plus 10% yield + /// @param depositor The address whose deposit to withdraw + /// @dev Only callable by the vault. Transfers principal + 10% yield. + function withdraw(address depositor) external { + if (msg.sender != vault) revert OnlyVault(); + + uint256 principal = deposits[depositor]; + if (principal == 0) revert NoDeposit(); + + // Clear deposit before transfer (CEI pattern) + deposits[depositor] = 0; + + // Calculate 10% yield + uint256 yieldAmount = principal / 10; + uint256 totalAmount = principal + yieldAmount; + + // Transfer to vault + (bool success,) = vault.call{value: totalAmount}(""); + if (!success) revert TransferFailed(); + + emit Withdrawn(depositor, principal, yieldAmount); + } + + // ============ View Functions ============ + + /// @notice Get the deposit amount for an address + /// @param depositor The address to check + /// @return The deposited amount + function getDeposit(address depositor) external view returns (uint256) { + return deposits[depositor]; + } + + /// @notice Get the projected withdrawal amount (principal + 10% yield) + /// @param depositor The address to check + /// @return The total amount that would be withdrawn + function getProjectedWithdrawal(address depositor) external view returns (uint256) { + uint256 principal = deposits[depositor]; + return principal + (principal / 10); + } +} diff --git a/crates/agents/examples/example-yield-agent/Cargo.toml b/crates/agents/examples/example-yield-agent/Cargo.toml new file mode 100644 index 0000000..47baa43 --- /dev/null +++ b/crates/agents/examples/example-yield-agent/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "example-yield-agent" +version = "0.1.0" +edition = "2021" +description = "Example yield agent for E2E testing with MockYieldSource integration" +license = "Apache-2.0" + +[lib] +crate-type = ["rlib"] + +[dependencies] +kernel-sdk = { path = "../../../sdk/kernel-sdk" } + +[build-dependencies] +sha2 = "0.10" diff --git a/crates/example-agent/build.rs b/crates/agents/examples/example-yield-agent/build.rs similarity index 54% rename from crates/example-agent/build.rs rename to crates/agents/examples/example-yield-agent/build.rs index e33c747..f1c2a83 100644 --- a/crates/example-agent/build.rs +++ b/crates/agents/examples/example-yield-agent/build.rs @@ -1,4 +1,4 @@ -//! Build script for example-agent +//! Build script for example-yield-agent //! //! Generates a compile-time constant `AGENT_CODE_HASH` that uniquely identifies //! this agent's source code. This hash is used by the kernel to verify that @@ -12,35 +12,6 @@ //! - `Cargo.toml` - Dependencies and build configuration //! //! The hash is computed as: SHA256(src/lib.rs || 0x00 || Cargo.toml) -//! -//! # Why Source Hashing (Temporary) -//! -//! Hashing compiled artifacts (.rlib) is complex because: -//! - rlib paths depend on build configuration and target -//! - Incremental compilation creates varying outputs -//! - Cross-compilation adds further complexity -//! -//! Source hashing provides determinism for development while we design -//! the production artifact hashing mechanism. -//! -//! # Production Considerations -//! -//! In production deployments with dynamic agents, this will be replaced by: -//! - Hashing the zkVM guest ELF binary -//! - Using the RISC Zero image ID -//! - Or hashing a canonical deployment artifact -//! -//! # Binding the Proof to Agent Code -//! -//! This hash creates a cryptographic binding between: -//! 1. The `agent_code_hash` declared in `KernelInputV1` (what the verifier expects) -//! 2. The actual agent code linked into the guest (what actually runs) -//! -//! If these don't match, the kernel rejects execution with `AgentCodeHashMismatch`. -//! This prevents: -//! - Proofs generated with one agent from being replayed with different agent code -//! - Malicious substitution of agent implementations -//! - Version confusion in multi-agent deployments use sha2::{Digest, Sha256}; use std::env; @@ -91,30 +62,16 @@ fn main() { writeln!(file, "// Auto-generated by build.rs - DO NOT EDIT").unwrap(); writeln!(file, "//").unwrap(); - writeln!(file, "// This hash uniquely identifies the agent source code.").unwrap(); + writeln!(file, "// This hash uniquely identifies the example-yield-agent source code.").unwrap(); writeln!(file, "// It is computed as SHA256(src/lib.rs || 0x00 || Cargo.toml).").unwrap(); - writeln!(file, "//").unwrap(); - writeln!(file, "// NOTE: This is a DEV hash based on source files.").unwrap(); - writeln!(file, "// Production deployments will use artifact hashing.").unwrap(); writeln!(file).unwrap(); writeln!( file, - "/// SHA-256 hash of the agent source code." + "/// SHA-256 hash of the yield agent source code." ) .unwrap(); writeln!(file, "///").unwrap(); writeln!(file, "/// This constant binds the zkVM proof to this specific agent implementation.").unwrap(); - writeln!(file, "/// The kernel verifies that `KernelInputV1.agent_code_hash` matches this value").unwrap(); - writeln!(file, "/// before executing the agent. A mismatch results in `KernelError::AgentCodeHashMismatch`.").unwrap(); - writeln!(file, "///").unwrap(); - writeln!(file, "/// # What is Hashed").unwrap(); - writeln!(file, "///").unwrap(); - writeln!(file, "/// DEV mode: `SHA256(src/lib.rs || 0x00 || Cargo.toml)`").unwrap(); - writeln!(file, "///").unwrap(); - writeln!(file, "/// # When This Changes").unwrap(); - writeln!(file, "///").unwrap(); - writeln!(file, "/// Any modification to the hashed source files will produce a new hash.").unwrap(); - writeln!(file, "/// This includes whitespace changes, comments, and dependency updates.").unwrap(); write!(file, "pub const AGENT_CODE_HASH: [u8; 32] = [").unwrap(); for (i, byte) in hash.iter().enumerate() { if i > 0 { @@ -126,5 +83,5 @@ fn main() { // Print hash for build logs (useful for debugging) let hash_hex: String = hash.iter().map(|b| format!("{:02x}", b)).collect(); - println!("cargo:warning=AGENT_CODE_HASH: {}", hash_hex); + println!("cargo:warning=AGENT_CODE_HASH (example-yield-agent): {}", hash_hex); } diff --git a/crates/agents/examples/example-yield-agent/src/lib.rs b/crates/agents/examples/example-yield-agent/src/lib.rs new file mode 100644 index 0000000..0b82f2a --- /dev/null +++ b/crates/agents/examples/example-yield-agent/src/lib.rs @@ -0,0 +1,299 @@ +//! Example Yield Agent Implementation +//! +//! This agent demonstrates E2E execution flow for yield farming: +//! 1. Deposit ETH to MockYieldSource +//! 2. Withdraw ETH + 10% yield from MockYieldSource +//! +//! # Input Format (48 bytes) +//! +//! ```text +//! [0:20] vault_address (20 bytes) +//! [20:40] mock_yield_address (20 bytes) +//! [40:48] transfer_amount (u64 LE) +//! ``` +//! +//! # Output Actions +//! +//! Two CALL actions targeting MockYieldSource: +//! 1. Deposit: `call{value: amount}("")` - sends ETH +//! 2. Withdraw: `call{value: 0}(withdraw(vault))` - triggers withdrawal +//! +//! # On-Chain Action Format (KernelOutputParser.sol) +//! +//! - `action_type`: 0x00000002 (ACTION_TYPE_CALL) +//! - `target`: bytes32 (address left-padded with 12 zero bytes) +//! - `payload`: abi.encode(uint256 value, bytes callData) + +#![no_std] +#![deny(unsafe_code)] + +extern crate alloc; + +use alloc::vec::Vec; +use kernel_sdk::prelude::*; + +// Include the generated agent hash constant. +include!(concat!(env!("OUT_DIR"), "/agent_hash.rs")); + +// ============================================================================ +// Constants +// ============================================================================ + +/// Expected input size: 20 (vault) + 20 (yield source) + 8 (amount) = 48 bytes +const INPUT_SIZE: usize = 48; + +/// Withdraw function selector: keccak256("withdraw(address)")[:4] +const WITHDRAW_SELECTOR: [u8; 4] = [0x51, 0xcf, 0xf8, 0xd9]; + +// ============================================================================ +// Agent Entry Point +// ============================================================================ + +/// Canonical agent entrypoint for the yield agent. +/// +/// # Arguments +/// +/// - `ctx`: Execution context (contains agent_id used for withdraw call) +/// - `opaque_inputs`: 48-byte input with addresses and amount +/// +/// # Returns +/// +/// AgentOutput with two CALL actions: deposit and withdraw. +#[no_mangle] +#[allow(unsafe_code)] +pub extern "Rust" fn agent_main(_ctx: &AgentContext, opaque_inputs: &[u8]) -> AgentOutput { + // Validate input size + if opaque_inputs.len() != INPUT_SIZE { + // Invalid input - return empty output (will be handled by constraints) + return AgentOutput { actions: Vec::new() }; + } + + // Parse input + let vault_address: [u8; 20] = opaque_inputs[0..20].try_into().unwrap(); + let mock_yield_address: [u8; 20] = opaque_inputs[20..40].try_into().unwrap(); + let transfer_amount = u64::from_le_bytes(opaque_inputs[40..48].try_into().unwrap()); + + // Build target (left-pad address to bytes32) + let target = address_to_bytes32(&mock_yield_address); + + // Build Action 1: Deposit ETH to MockYieldSource + // call{value: amount}("") - sends ETH with empty calldata + let deposit_action = call_action(target, transfer_amount as u128, &[]); + + // Build Action 2: Withdraw from MockYieldSource + // call{value: 0}(withdraw(vault_address)) + let withdraw_calldata = encode_withdraw_call(&vault_address); + let withdraw_action = call_action(target, 0, &withdraw_calldata); + + // Return both actions (deposit first, then withdraw) + let mut actions = Vec::with_capacity(2); + actions.push(deposit_action); + actions.push(withdraw_action); + + AgentOutput { actions } +} + +// ============================================================================ +// ABI Encoding Helpers +// ============================================================================ + +/// Encode the withdraw(address) function call. +/// +/// Format: selector (4 bytes) + address (32 bytes, left-padded) +fn encode_withdraw_call(depositor: &[u8; 20]) -> Vec { + let mut calldata = Vec::with_capacity(36); + calldata.extend_from_slice(&WITHDRAW_SELECTOR); + calldata.extend_from_slice(&address_to_bytes32(depositor)); + calldata +} + + +// ============================================================================ +// Compile-time ABI Verification +// ============================================================================ + +/// Compile-time check that agent_main matches the canonical AgentEntrypoint type. +const _: AgentEntrypoint = agent_main; + +// ============================================================================ +// Tests +// ============================================================================ + +#[cfg(test)] +mod tests { + use super::*; + + fn make_test_input(vault: [u8; 20], yield_source: [u8; 20], amount: u64) -> Vec { + let mut input = Vec::with_capacity(INPUT_SIZE); + input.extend_from_slice(&vault); + input.extend_from_slice(&yield_source); + input.extend_from_slice(&amount.to_le_bytes()); + input + } + + #[test] + fn test_agent_main_produces_two_actions() { + let ctx = AgentContext { + protocol_version: 1, + kernel_version: 1, + agent_id: [0x42u8; 32], + agent_code_hash: AGENT_CODE_HASH, + constraint_set_hash: [0xbb; 32], + input_root: [0xcc; 32], + execution_nonce: 1, + }; + + let vault = [0x11u8; 20]; + let yield_source = [0x22u8; 20]; + let amount: u64 = 1_000_000_000_000_000_000; // 1 ETH + + let input = make_test_input(vault, yield_source, amount); + let output = agent_main(&ctx, &input); + + assert_eq!(output.actions.len(), 2, "Expected 2 actions"); + + // Both actions should target the yield source + let expected_target = address_to_bytes32(&yield_source); + assert_eq!(output.actions[0].target, expected_target, "Deposit target mismatch"); + assert_eq!(output.actions[1].target, expected_target, "Withdraw target mismatch"); + + // Both actions should be CALL type + assert_eq!(output.actions[0].action_type, ACTION_TYPE_CALL); + assert_eq!(output.actions[1].action_type, ACTION_TYPE_CALL); + } + + #[test] + fn test_deposit_action_payload_format() { + let ctx = AgentContext { + protocol_version: 1, + kernel_version: 1, + agent_id: [0x42u8; 32], + agent_code_hash: AGENT_CODE_HASH, + constraint_set_hash: [0xbb; 32], + input_root: [0xcc; 32], + execution_nonce: 1, + }; + + let vault = [0x11u8; 20]; + let yield_source = [0x22u8; 20]; + let amount: u64 = 1_000_000_000_000_000_000; // 1 ETH + + let input = make_test_input(vault, yield_source, amount); + let output = agent_main(&ctx, &input); + + let deposit_payload = &output.actions[0].payload; + + // Should be 96 bytes (32 + 32 + 32 + 0 padded) + assert_eq!(deposit_payload.len(), 96, "Deposit payload should be 96 bytes"); + + // Check value encoding (bytes 0-31) + let value_bytes = &deposit_payload[0..32]; + // Value should be in last 8 bytes for u64 (big-endian in u256) + assert_eq!(&value_bytes[24..32], &amount.to_be_bytes()); + + // Check offset (bytes 32-63) = 64 + let offset_bytes = &deposit_payload[32..64]; + assert_eq!(offset_bytes[31], 64); // Last byte should be 64 + + // Check length (bytes 64-95) = 0 + let length_bytes = &deposit_payload[64..96]; + assert_eq!(length_bytes, &[0u8; 32]); // All zeros for empty data + } + + #[test] + fn test_withdraw_action_payload_format() { + let ctx = AgentContext { + protocol_version: 1, + kernel_version: 1, + agent_id: [0x42u8; 32], + agent_code_hash: AGENT_CODE_HASH, + constraint_set_hash: [0xbb; 32], + input_root: [0xcc; 32], + execution_nonce: 1, + }; + + let vault = [0x11u8; 20]; + let yield_source = [0x22u8; 20]; + let amount: u64 = 1_000_000_000_000_000_000; + + let input = make_test_input(vault, yield_source, amount); + let output = agent_main(&ctx, &input); + + let withdraw_payload = &output.actions[1].payload; + + // payload = 32 (value) + 32 (offset) + 32 (length) + 64 (padded calldata) + // 36 bytes padded to 32-byte boundary = 64 bytes + assert_eq!(withdraw_payload.len(), 160, "Withdraw payload should be 160 bytes"); + + // Check value = 0 (bytes 0-31) + assert_eq!(&withdraw_payload[0..32], &[0u8; 32]); + + // Check offset = 64 (bytes 32-63) + assert_eq!(withdraw_payload[63], 64); + + // Check length = 36 (bytes 64-95) + assert_eq!(withdraw_payload[95], 36); + + // Check selector (bytes 96-99) + assert_eq!(&withdraw_payload[96..100], &WITHDRAW_SELECTOR); + + // Check vault address (bytes 100-131, left-padded) + assert_eq!(&withdraw_payload[100..112], &[0u8; 12]); // Padding + assert_eq!(&withdraw_payload[112..132], &vault); + } + + #[test] + fn test_withdraw_calldata_encoding() { + let vault = [0x11u8; 20]; + let calldata = encode_withdraw_call(&vault); + + // calldata = 4 (selector) + 32 (address) = 36 bytes + assert_eq!(calldata.len(), 36, "Withdraw calldata should be 36 bytes"); + + // Check selector + assert_eq!(&calldata[0..4], &WITHDRAW_SELECTOR); + + // Check vault address (left-padded to 32 bytes) + assert_eq!(&calldata[4..16], &[0u8; 12]); // Padding + assert_eq!(&calldata[16..36], &vault); + } + + #[test] + fn test_address_to_bytes32_from_sdk() { + let addr = [0xabu8; 20]; + let result = address_to_bytes32(&addr); + + assert_eq!(&result[0..12], &[0u8; 12], "First 12 bytes should be 0"); + assert_eq!(&result[12..32], &addr, "Last 20 bytes should be address"); + } + + #[test] + fn test_invalid_input_size_returns_empty() { + let ctx = AgentContext { + protocol_version: 1, + kernel_version: 1, + agent_id: [0x42u8; 32], + agent_code_hash: AGENT_CODE_HASH, + constraint_set_hash: [0xbb; 32], + input_root: [0xcc; 32], + execution_nonce: 1, + }; + + // Test too short + let short_input = alloc::vec![0u8; 40]; + let output = agent_main(&ctx, &short_input); + assert!(output.actions.is_empty(), "Short input should produce empty output"); + + // Test too long + let long_input = alloc::vec![0u8; 50]; + let output = agent_main(&ctx, &long_input); + assert!(output.actions.is_empty(), "Long input should produce empty output"); + } + + #[test] + fn test_withdraw_selector_is_correct() { + // keccak256("withdraw(address)") = 0x51cff8d9... + // This is the standard selector for withdraw(address) + assert_eq!(WITHDRAW_SELECTOR, [0x51, 0xcf, 0xf8, 0xd9]); + } +} diff --git a/crates/agents/wrappers/kernel-guest-binding-yield/Cargo.toml b/crates/agents/wrappers/kernel-guest-binding-yield/Cargo.toml new file mode 100644 index 0000000..abbc986 --- /dev/null +++ b/crates/agents/wrappers/kernel-guest-binding-yield/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "kernel-guest-binding-yield" +version = "0.1.0" +edition = "2021" +description = "Wrapper crate binding example-yield-agent to kernel-guest" +license = "Apache-2.0" + +[lib] +crate-type = ["rlib"] + +[dependencies] +kernel-guest = { path = "../../../runtime/kernel-guest" } +kernel-sdk = { path = "../../../sdk/kernel-sdk" } +kernel-core = { path = "../../../protocol/kernel-core", default-features = false } +constraints = { path = "../../../protocol/constraints" } +example-yield-agent = { path = "../../examples/example-yield-agent" } + +[features] +default = [] +risc0 = ["kernel-guest/risc0"] diff --git a/crates/agents/wrappers/kernel-guest-binding-yield/src/lib.rs b/crates/agents/wrappers/kernel-guest-binding-yield/src/lib.rs new file mode 100644 index 0000000..c4c0b0f --- /dev/null +++ b/crates/agents/wrappers/kernel-guest-binding-yield/src/lib.rs @@ -0,0 +1,61 @@ +//! Wrapper crate binding example-yield-agent to kernel-guest. +//! +//! This crate implements [`kernel_guest::AgentEntrypoint`] for the example-yield-agent, +//! allowing it to be used with the agent-agnostic kernel execution functions. +//! +//! # Usage +//! +//! ```ignore +//! // In a zkVM guest main.rs or test: +//! let result = guest_wrapper_yield_agent::kernel_main(&input_bytes)?; +//! ``` + +use kernel_guest::AgentEntrypoint; +use kernel_sdk::agent::AgentContext; +use kernel_core::AgentOutput; + +// Re-export the agent code hash for convenience. +pub use example_yield_agent::AGENT_CODE_HASH; + +/// Wrapper implementing [`AgentEntrypoint`] for the example-yield-agent. +pub struct YieldAgentWrapper; + +impl AgentEntrypoint for YieldAgentWrapper { + fn code_hash(&self) -> [u8; 32] { + example_yield_agent::AGENT_CODE_HASH + } + + fn run(&self, ctx: &AgentContext, opaque_inputs: &[u8]) -> AgentOutput { + example_yield_agent::agent_main(ctx, opaque_inputs) + } +} + +/// Convenience function for kernel execution with the yield-agent. +/// +/// This is equivalent to calling: +/// ```ignore +/// kernel_guest::kernel_main_with_agent(input_bytes, &YieldAgentWrapper) +/// ``` +pub fn kernel_main(input_bytes: &[u8]) -> Result, kernel_guest::KernelError> { + kernel_guest::kernel_main_with_agent(input_bytes, &YieldAgentWrapper) +} + +/// Convenience function for kernel execution with the yield-agent and custom constraints. +/// +/// This is equivalent to calling: +/// ```ignore +/// kernel_guest::kernel_main_with_agent_and_constraints(input_bytes, &YieldAgentWrapper, constraint_set) +/// ``` +pub fn kernel_main_with_constraints( + input_bytes: &[u8], + constraint_set: &constraints::ConstraintSetV1, +) -> Result, kernel_guest::KernelError> { + kernel_guest::kernel_main_with_agent_and_constraints( + input_bytes, + &YieldAgentWrapper, + constraint_set, + ) +} + +// Re-export kernel_guest types for convenience. +pub use kernel_guest::KernelError; diff --git a/crates/e2e-tests/Cargo.toml b/crates/e2e-tests/Cargo.toml deleted file mode 100644 index 6028b1f..0000000 --- a/crates/e2e-tests/Cargo.toml +++ /dev/null @@ -1,40 +0,0 @@ -[package] -name = "e2e-tests" -version = "0.1.0" -edition = "2021" -description = "End-to-end zkVM proof tests for the execution kernel" -license = "Apache-2.0" -publish = false - -# This crate contains only tests, no library -[lib] -path = "src/lib.rs" - -[dependencies] -# Kernel crates for types and encoding -kernel-core = { path = "../kernel-core" } -kernel-sdk = { path = "../kernel-sdk" } -constraints = { path = "../constraints" } - -# Agent code hash for input construction -example-agent = { path = "../example-agent" } - -# Methods crate provides the guest ELF and IMAGE_ID -methods = { path = "../methods", optional = true } - -# RISC Zero prover and verifier (host-side) -risc0-zkvm = { version = "3.0", default-features = false, optional = true } - -[dev-dependencies] -# Same dependencies for tests -kernel-core = { path = "../kernel-core" } -kernel-sdk = { path = "../kernel-sdk" } -constraints = { path = "../constraints" } -example-agent = { path = "../example-agent" } -hex = "0.4" - -[features] -default = [] -# Enable this feature to run zkVM proof tests -# Requires RISC Zero toolchain installed -risc0-e2e = ["dep:methods", "dep:risc0-zkvm", "risc0-zkvm/prove"] diff --git a/crates/e2e-tests/README.md b/crates/e2e-tests/README.md deleted file mode 100644 index 7ae8533..0000000 --- a/crates/e2e-tests/README.md +++ /dev/null @@ -1,177 +0,0 @@ -# E2E zkVM Proof Tests - -End-to-end integration tests that verify the complete execution kernel flow using RISC Zero zkVM proofs. - -## Overview - -These tests verify: - -1. **Agent → Guest → Proof → Verification** pipeline works correctly -2. **Agent code hash binding** prevents unauthorized agent substitution -3. **Determinism** - same input always produces same output -4. **Commitment integrity** - input and action commitments are correctly computed - -## Prerequisites - -### Install RISC Zero Toolchain - -```bash -# Install cargo-risczero -cargo install cargo-risczero - -# Install the RISC Zero toolchain (includes riscv32im target) -cargo risczero install -``` - -### Verify Installation - -```bash -cargo risczero --version -``` - -## Running Tests - -### Unit Tests (no zkVM required) - -```bash -# Run unit tests without proof generation -cargo test -p e2e-tests -``` - -### Full E2E Proof Tests - -```bash -# Run with zkVM proof generation (requires RISC Zero toolchain) -cargo test -p e2e-tests --features risc0-e2e -- --nocapture -``` - -### Individual Test - -```bash -# Run specific test -cargo test -p e2e-tests --features risc0-e2e test_e2e_success_with_echo -- --nocapture -``` - -## Test Cases - -### 1. `test_e2e_success_with_echo` - -Verifies the happy path: -- Valid input with `opaque_inputs[0] == 1` (echo trigger) -- Proof generation succeeds -- Receipt verifies against IMAGE_ID -- Journal contains correct: - - `execution_status == Success` - - `input_commitment == SHA256(input_bytes)` - - `action_commitment == SHA256(encoded_echo_output)` - -### 2. `test_e2e_agent_code_hash_mismatch` - -Verifies security: -- Input with wrong `agent_code_hash` (all zeros) -- Guest execution fails with `AgentCodeHashMismatch` -- No valid proof/receipt is produced - -### 3. `test_e2e_empty_output` - -Verifies empty output handling: -- Input with `opaque_inputs[0] != 1` (no echo) -- Proof generation succeeds (empty output is valid) -- `action_commitment == EMPTY_OUTPUT_COMMITMENT` - -### 4. `test_e2e_determinism` - -Verifies deterministic execution: -- Same input run twice -- Both runs produce identical journal bytes - -## CI Integration - -These tests are **feature-gated** to avoid requiring RISC Zero in all CI environments: - -```yaml -# In CI, only run E2E tests in environments with RISC Zero installed -- name: Run E2E proof tests - if: ${{ matrix.risc0-enabled }} - run: cargo test -p e2e-tests --features risc0-e2e -``` - -For CI without RISC Zero: - -```yaml -# Unit tests always work -- name: Run unit tests - run: cargo test -p e2e-tests -``` - -## Reproducible Builds - -For deterministic guest ELF builds (useful for IMAGE_ID reproducibility): - -```bash -RISC0_USE_DOCKER=1 cargo test -p e2e-tests --features risc0-e2e -``` - -This requires Docker and uses the official RISC Zero Docker image. - -## Troubleshooting - -### "risc0-zkvm not found" - -Install the RISC Zero toolchain: - -```bash -cargo risczero install -``` - -### "failed to find riscv32im-risc0-zkvm-elf target" - -The RISC Zero toolchain may not be installed correctly. Reinstall: - -```bash -cargo risczero install --force -``` - -### Slow proof generation - -Proof generation can take several minutes. For faster iteration during development, you can use the `dev` prover mode (add `--features dev-mode` if available in your risc0-zkvm version). - -## Architecture - -``` -┌─────────────────────────────────────────────────────────────────┐ -│ e2e-tests │ -│ ┌────────────────┐ ┌────────────────┐ ┌────────────────┐ │ -│ │ Test Input │──▶│ methods crate │──▶│ Prover │ │ -│ │ (KernelInputV1)│ │ (ELF+IMAGE_ID) │ │ (risc0-zkvm) │ │ -│ └────────────────┘ └────────────────┘ └───────┬────────┘ │ -│ │ │ -│ ┌────────▼────────┐ │ -│ │ Receipt │ │ -│ │ (Proof+Journal) │ │ -│ └────────┬────────┘ │ -│ │ │ -│ ┌────────────────┐ ┌────────▼────────┐ │ -│ │ Decode Journal │◀───────────────────────│ Verify Receipt │ │ -│ │ (KernelJournalV1)│ │ (IMAGE_ID) │ │ -│ └────────┬───────┘ └─────────────────┘ │ -│ │ │ -│ ┌────────▼───────┐ │ -│ │ Assert Fields │ │ -│ │ (status, etc.) │ │ -│ └────────────────┘ │ -└─────────────────────────────────────────────────────────────────┘ -``` - -## Files - -- `src/lib.rs` - Test implementations and helper functions -- `Cargo.toml` - Dependencies with `risc0-e2e` feature gate -- `README.md` - This file - -## Related Crates - -- `methods` - Builds kernel-guest as RISC Zero guest, exports ELF/IMAGE_ID -- `kernel-guest` - The guest program that runs in zkVM -- `example-agent` - Provides `agent_main` and `AGENT_CODE_HASH` -- `kernel-core` - Types and encoding used by both host and guest diff --git a/crates/example-agent/Cargo.toml b/crates/example-agent/Cargo.toml deleted file mode 100644 index d8e3aa2..0000000 --- a/crates/example-agent/Cargo.toml +++ /dev/null @@ -1,15 +0,0 @@ -[package] -name = "example-agent" -version = "0.1.0" -edition = "2021" -description = "Example agent implementation for testing the canonical agent_main entrypoint" -license = "Apache-2.0" - -[lib] -crate-type = ["rlib"] - -[dependencies] -kernel-sdk = { path = "../kernel-sdk" } - -[build-dependencies] -sha2 = "0.10" diff --git a/crates/example-agent/src/lib.rs b/crates/example-agent/src/lib.rs deleted file mode 100644 index efe172a..0000000 --- a/crates/example-agent/src/lib.rs +++ /dev/null @@ -1,154 +0,0 @@ -//! Example Agent Implementation -//! -//! This crate provides a minimal example agent that demonstrates the canonical -//! `agent_main` entrypoint. It is used for testing the kernel's agent execution -//! flow. -//! -//! # Behavior -//! -//! The agent checks the first byte of `opaque_inputs`: -//! - If `opaque_inputs[0] == 1` → Echo action with full inputs as payload -//! - Otherwise → Empty output (no actions) -//! -//! This allows testing both action-producing and no-action execution paths. -//! -//! # Agent Code Hash -//! -//! This crate exports [`AGENT_CODE_HASH`], a compile-time constant that uniquely -//! identifies this agent's source code. The kernel uses this to verify that the -//! `agent_code_hash` field in `KernelInputV1` matches the linked agent. -//! -//! See the `build.rs` file for details on how the hash is computed. - -#![no_std] -// We use `deny` instead of `forbid` because `#[no_mangle]` triggers the -// unsafe_code lint in modern Rust (symbol collisions are UB). The allow -// is scoped to only the agent_main function. -#![deny(unsafe_code)] - -extern crate alloc; - -use kernel_sdk::prelude::*; - -// Include the generated agent hash constant. -// This is created by build.rs and contains AGENT_CODE_HASH. -include!(concat!(env!("OUT_DIR"), "/agent_hash.rs")); - -/// Canonical agent entrypoint. -/// -/// This function is called by the kernel to execute the agent logic. -/// The symbol name `agent_main` is mandatory and fixed. -/// -/// # Arguments -/// -/// - `ctx`: Execution context with identity and metadata -/// - `opaque_inputs`: Agent-specific input data -/// -/// # Symbol Requirements -/// -/// The `#[no_mangle]` attribute is required so the kernel can find this symbol. -/// The symbol name `agent_main` is fixed by the kernel ABI specification. -/// -/// # Lint Exception -/// -/// `#[no_mangle]` triggers the `unsafe_code` lint because symbol collisions -/// can cause undefined behavior. The `#[allow(unsafe_code)]` is scoped to -/// this function only, and is safe because: -/// - The symbol `agent_main` is the canonical entrypoint defined by the kernel ABI -/// - Only one agent is linked per guest binary -/// - The kernel explicitly expects this exact symbol name -#[no_mangle] -#[allow(unsafe_code)] -pub extern "Rust" fn agent_main(ctx: &AgentContext, opaque_inputs: &[u8]) -> AgentOutput { - let should_echo = !opaque_inputs.is_empty() && opaque_inputs[0] == 1; - - if should_echo { - // Truncate to max payload size if needed - let payload_len = opaque_inputs.len().min(MAX_ACTION_PAYLOAD_BYTES); - let mut payload = Vec::with_capacity(payload_len); - payload.extend_from_slice(&opaque_inputs[..payload_len]); - - let action = echo_action(ctx.agent_id, payload); - - let mut actions = Vec::with_capacity(1); - actions.push(action); - AgentOutput { actions } - } else { - AgentOutput { actions: Vec::new() } - } -} - -// ============================================================================ -// Compile-time ABI Verification -// ============================================================================ - -/// Compile-time check that agent_main matches the canonical AgentEntrypoint type. -/// -/// This ensures the function signature is correct at compile time rather than -/// failing at link time with a cryptic error. -/// -/// NOTE: `extern "Rust"` requires the agent and kernel to be compiled with -/// the same rustc toolchain. The workspace pins the toolchain for determinism. -const _: AgentEntrypoint = agent_main; - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_agent_main_echo() { - let ctx = AgentContext { - protocol_version: 1, - kernel_version: 1, - agent_id: [0x42u8; 32], - agent_code_hash: [0xaau8; 32], - constraint_set_hash: [0xbbu8; 32], - input_root: [0xccu8; 32], - execution_nonce: 1, - }; - - let inputs = [1u8, 2, 3, 4, 5]; // First byte is 1 -> should echo - let output = agent_main(&ctx, &inputs); - - assert_eq!(output.actions.len(), 1); - assert_eq!(output.actions[0].action_type, ACTION_TYPE_ECHO); - assert_eq!(output.actions[0].target, ctx.agent_id); - assert_eq!(output.actions[0].payload, inputs.to_vec()); - } - - #[test] - fn test_agent_main_no_echo() { - let ctx = AgentContext { - protocol_version: 1, - kernel_version: 1, - agent_id: [0x42u8; 32], - agent_code_hash: [0xaau8; 32], - constraint_set_hash: [0xbbu8; 32], - input_root: [0xccu8; 32], - execution_nonce: 1, - }; - - let inputs = [0u8, 2, 3, 4, 5]; // First byte is 0 -> no echo - let output = agent_main(&ctx, &inputs); - - assert!(output.actions.is_empty()); - } - - #[test] - fn test_agent_main_empty_inputs() { - let ctx = AgentContext { - protocol_version: 1, - kernel_version: 1, - agent_id: [0x42u8; 32], - agent_code_hash: [0xaau8; 32], - constraint_set_hash: [0xbbu8; 32], - input_root: [0xccu8; 32], - execution_nonce: 1, - }; - - let inputs: [u8; 0] = []; // Empty -> no echo - let output = agent_main(&ctx, &inputs); - - assert!(output.actions.is_empty()); - } -} diff --git a/crates/host-tests/Cargo.toml b/crates/host-tests/Cargo.toml deleted file mode 100644 index 415eef8..0000000 --- a/crates/host-tests/Cargo.toml +++ /dev/null @@ -1,16 +0,0 @@ -[package] -name = "host-tests" -version = "0.1.0" -edition = "2021" - -[dependencies] -kernel-core = { path = "../kernel-core", features = ["std"] } -kernel-guest = { path = "../kernel-guest" } -kernel-sdk = { path = "../kernel-sdk" } -constraints = { path = "../constraints" } -# example-agent provides the agent_main symbol required by kernel-guest -example-agent = { path = "../example-agent" } - -[dev-dependencies] -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" \ No newline at end of file diff --git a/crates/kernel-guest/Cargo.toml b/crates/kernel-guest/Cargo.toml deleted file mode 100644 index 64da5a6..0000000 --- a/crates/kernel-guest/Cargo.toml +++ /dev/null @@ -1,20 +0,0 @@ -[package] -name = "kernel-guest" -version = "0.1.0" -edition = "2021" -description = "Kernel guest execution logic with agent_main entrypoint" -license = "Apache-2.0" - -[dependencies] -kernel-core = { path = "../kernel-core", default-features = false } -kernel-sdk = { path = "../kernel-sdk" } -constraints = { path = "../constraints" } -example-agent = { path = "../example-agent", optional = true } - -# RISC Zero zkVM guest runtime (only needed when building for zkVM) -risc0-zkvm = { version = "3.0", default-features = false, features = ["std"], optional = true } - -[features] -default = ["example-agent"] -risc0 = ["dep:risc0-zkvm"] -example-agent = ["dep:example-agent"] \ No newline at end of file diff --git a/crates/kernel-guest/src/lib.rs b/crates/kernel-guest/src/lib.rs deleted file mode 100644 index e2507d8..0000000 --- a/crates/kernel-guest/src/lib.rs +++ /dev/null @@ -1,304 +0,0 @@ -//! Kernel Guest Execution Logic -//! -//! This crate implements the core kernel execution logic that runs inside -//! the zkVM guest. It orchestrates agent execution through the canonical -//! `agent_main` entrypoint. -//! -//! # Execution Flow -//! -//! 1. Decode input bytes → `KernelInputV1` -//! 2. Validate protocol and kernel versions -//! 3. **Verify agent code hash matches linked agent** (P0.5) -//! 4. Compute input commitment (SHA256) -//! 5. Build `AgentContext` from kernel input -//! 6. Call `agent_main` via `extern "Rust"` ABI -//! 7. Enforce constraints on agent output (UNSKIPPABLE) -//! 8. Compute action commitment (SHA256) -//! 9. Return encoded `KernelJournalV1` -//! -//! # Agent Entrypoint -//! -//! The agent is invoked through an `extern "Rust"` function with the symbol -//! `agent_main`. This function is linked at compile time (for static -//! agents like `example-agent`) or at load time (for dynamic agents). -//! -//! # Agent Code Hash Binding (P0.5) -//! -//! When the `example-agent` feature is enabled, the kernel verifies that -//! `KernelInputV1.agent_code_hash` matches the hash of the linked agent -//! (`example_agent::AGENT_CODE_HASH`). This binding ensures: -//! -//! - Proofs are tied to the specific agent implementation that ran -//! - Malicious agent substitution is detected and rejected -//! - Verifiers can trust that the claimed agent actually executed -//! -//! If the hash doesn't match, `KernelError::AgentCodeHashMismatch` is returned. - -use kernel_core::*; -use kernel_sdk::agent::AgentContext; -use constraints::{enforce_constraints, ConstraintSetV1, EMPTY_OUTPUT_COMMITMENT}; - -// Import the agent code hash when example-agent feature is enabled. -// This constant is generated at build time by example-agent's build.rs. -#[cfg(feature = "example-agent")] -use example_agent::AGENT_CODE_HASH as LINKED_AGENT_CODE_HASH; - -// ============================================================================ -// Agent Entrypoint Declaration -// ============================================================================ - -// The agent_main function is provided by the linked agent crate. -// For example, when the `example-agent` feature is enabled, the -// example_agent::agent_main function is linked. -// -// Using `extern "Rust"` is safe for Rust types like &AgentContext and -// AgentOutput (which contains Vec). This avoids the ABI-safety issues -// that would arise with `extern "C"`. -// -// IMPORTANT: `extern "Rust"` requires the agent and kernel to be compiled -// with the same rustc toolchain. The workspace pins the toolchain via -// rust-toolchain.toml to ensure deterministic builds and ABI compatibility. -extern "Rust" { - fn agent_main(ctx: &AgentContext, opaque_inputs: &[u8]) -> AgentOutput; -} - -/// Safe wrapper for calling the agent entrypoint. -#[inline] -fn call_agent(ctx: &AgentContext, opaque_inputs: &[u8]) -> AgentOutput { - // Safe: extern "Rust" guarantees correct ABI for Rust types - unsafe { agent_main(ctx, opaque_inputs) } -} - -// ============================================================================ -// Main Kernel Execution -// ============================================================================ - -/// Main kernel execution function. -/// -/// This is the core execution logic that: -/// 1. Decodes and validates input -/// 2. Verifies protocol and kernel versions -/// 3. Verifies agent code hash matches linked agent (P0.5) -/// 4. Computes input commitment -/// 5. Executes the agent via `agent_main` -/// 6. Enforces constraints on agent output (UNSKIPPABLE) -/// 7. Computes action commitment -/// 8. Constructs and returns the journal -/// -/// # P0.5 Agent Code Hash Binding -/// -/// When the `example-agent` feature is enabled, step 3 verifies that -/// `KernelInputV1.agent_code_hash` matches the compile-time constant -/// `LINKED_AGENT_CODE_HASH` from the linked agent crate. This ensures -/// proofs are cryptographically bound to specific agent code. -/// -/// # P0.3 Constraint Enforcement -/// -/// Constraints are ALWAYS enforced after agent execution. If any constraint -/// is violated: -/// - `execution_status` is set to `Failure` (0x02) -/// - `action_commitment` is computed over an empty `AgentOutput` -/// - A valid journal is still produced -/// -/// This ensures constraint violations are provable and verifiable on-chain. -/// -/// # Arguments -/// * `input_bytes` - Canonical encoding of KernelInputV1 -/// -/// # Returns -/// * `Ok(Vec)` - Canonical encoding of KernelJournalV1 (always produced) -/// * `Err(KernelError)` - Critical failure (decoding, version mismatch) -/// -/// # Determinism -/// -/// This function is fully deterministic. Same input bytes will -/// always produce the same output bytes across: -/// - Different machines -/// - Different provers -/// - Rebuilds with pinned toolchain -pub fn kernel_main(input_bytes: &[u8]) -> Result, KernelError> { - // 1. Decode input - let input = KernelInputV1::decode(input_bytes)?; - - // 2. Validate versions (already checked in decode, but be explicit) - if input.protocol_version != PROTOCOL_VERSION { - return Err(KernelError::UnsupportedProtocolVersion { - expected: PROTOCOL_VERSION, - actual: input.protocol_version, - }); - } - - if input.kernel_version != KERNEL_VERSION { - return Err(KernelError::UnsupportedKernelVersion { - expected: KERNEL_VERSION, - actual: input.kernel_version, - }); - } - - // 3. Verify agent code hash matches linked agent (P0.5 binding) - // - // This check ensures that the agent_code_hash declared in the input - // matches the actual agent linked into this guest binary. Without this, - // a malicious prover could claim they ran agent X but actually run agent Y. - // - // When example-agent feature is enabled, we compare against the - // compile-time constant LINKED_AGENT_CODE_HASH from example_agent crate. - // When the feature is disabled, there is no linked agent to verify. - #[cfg(feature = "example-agent")] - { - if input.agent_code_hash != LINKED_AGENT_CODE_HASH { - return Err(KernelError::AgentCodeHashMismatch); - } - } - - // 4. Compute input commitment (over full input bytes) - let input_commitment = compute_input_commitment(input_bytes); - - // 5. Build agent context from input (using kernel-sdk AgentContext) - let agent_ctx = AgentContext::new( - input.protocol_version, - input.kernel_version, - input.agent_id, - input.agent_code_hash, - input.constraint_set_hash, - input.input_root, - input.execution_nonce, - ); - - // 6. Execute agent via canonical agent_main entrypoint - let agent_output = call_agent(&agent_ctx, &input.opaque_agent_inputs); - - // 7. Get constraint set (P0.3: use default permissive constraints) - let constraint_set = ConstraintSetV1::default(); - - // 8. ENFORCE CONSTRAINTS (UNSKIPPABLE) - // This is the critical safety check that validates all agent actions. - let (validated_output, execution_status) = - match enforce_constraints(&input, &agent_output, &constraint_set) { - Ok(validated) => { - // Constraints passed - use validated output - (validated, ExecutionStatus::Success) - } - Err(_violation) => { - // Constraints violated - use empty output and Failure status - // The violation details are not included in the journal for P0.3 - // but could be logged or added in future versions. - (AgentOutput { actions: vec![] }, ExecutionStatus::Failure) - } - }; - - // 9. Compute action commitment - // On Success: computed over validated output - // On Failure: computed over empty output (deterministic constant) - let action_commitment = if execution_status == ExecutionStatus::Success { - let output_bytes = validated_output - .encode() - .map_err(KernelError::EncodingFailed)?; - compute_action_commitment(&output_bytes) - } else { - // Use pre-computed constant for empty output commitment - EMPTY_OUTPUT_COMMITMENT - }; - - // 10. Construct journal with all identity and commitment fields - let journal = KernelJournalV1 { - protocol_version: PROTOCOL_VERSION, - kernel_version: KERNEL_VERSION, - agent_id: input.agent_id, - agent_code_hash: input.agent_code_hash, - constraint_set_hash: input.constraint_set_hash, - input_root: input.input_root, - execution_nonce: input.execution_nonce, - input_commitment, - action_commitment, - execution_status, - }; - - // 10. Encode and return journal (always produced) - journal.encode().map_err(KernelError::EncodingFailed) -} - -/// Execute kernel with custom constraint set. -/// -/// This variant allows specifying a custom constraint set instead of -/// using the default. Useful for testing and specialized deployments. -pub fn kernel_main_with_constraints( - input_bytes: &[u8], - constraint_set: &ConstraintSetV1, -) -> Result, KernelError> { - // 1. Decode input - let input = KernelInputV1::decode(input_bytes)?; - - // 2. Validate versions - if input.protocol_version != PROTOCOL_VERSION { - return Err(KernelError::UnsupportedProtocolVersion { - expected: PROTOCOL_VERSION, - actual: input.protocol_version, - }); - } - - if input.kernel_version != KERNEL_VERSION { - return Err(KernelError::UnsupportedKernelVersion { - expected: KERNEL_VERSION, - actual: input.kernel_version, - }); - } - - // 3. Verify agent code hash matches linked agent (P0.5 binding) - #[cfg(feature = "example-agent")] - { - if input.agent_code_hash != LINKED_AGENT_CODE_HASH { - return Err(KernelError::AgentCodeHashMismatch); - } - } - - // 4. Compute input commitment - let input_commitment = compute_input_commitment(input_bytes); - - // 5. Build agent context (using kernel-sdk AgentContext) - let agent_ctx = AgentContext::new( - input.protocol_version, - input.kernel_version, - input.agent_id, - input.agent_code_hash, - input.constraint_set_hash, - input.input_root, - input.execution_nonce, - ); - - // 6. Execute agent via canonical agent_main entrypoint - let agent_output = call_agent(&agent_ctx, &input.opaque_agent_inputs); - - // 7. ENFORCE CONSTRAINTS (UNSKIPPABLE) - let (validated_output, execution_status) = - match enforce_constraints(&input, &agent_output, constraint_set) { - Ok(validated) => (validated, ExecutionStatus::Success), - Err(_violation) => (AgentOutput { actions: vec![] }, ExecutionStatus::Failure), - }; - - // 8. Compute action commitment - let action_commitment = if execution_status == ExecutionStatus::Success { - let output_bytes = validated_output - .encode() - .map_err(KernelError::EncodingFailed)?; - compute_action_commitment(&output_bytes) - } else { - EMPTY_OUTPUT_COMMITMENT - }; - - // 9. Construct and return journal - let journal = KernelJournalV1 { - protocol_version: PROTOCOL_VERSION, - kernel_version: KERNEL_VERSION, - agent_id: input.agent_id, - agent_code_hash: input.agent_code_hash, - constraint_set_hash: input.constraint_set_hash, - input_root: input.input_root, - execution_nonce: input.execution_nonce, - input_commitment, - action_commitment, - execution_status, - }; - - journal.encode().map_err(KernelError::EncodingFailed) -} diff --git a/crates/kernel-guest/src/main.rs b/crates/kernel-guest/src/main.rs deleted file mode 100644 index fb10324..0000000 --- a/crates/kernel-guest/src/main.rs +++ /dev/null @@ -1,42 +0,0 @@ -//! RISC Zero zkVM Guest Entry Point -//! -//! This binary is compiled as a RISC Zero guest program. When executed in the zkVM: -//! 1. Reads `KernelInputV1` bytes from the host via `env::read()` -//! 2. Executes `kernel_main()` which runs the agent and enforces constraints -//! 3. Commits the `KernelJournalV1` bytes to the journal via `env::commit()` -//! -//! The journal commitment is included in the proof receipt and can be verified -//! by the host along with the IMAGE_ID. -//! -//! # Error Handling -//! -//! If kernel execution fails (e.g., version mismatch, agent code hash mismatch), -//! the guest panics. This aborts proof generation - no valid receipt is produced. -//! This is the correct behavior: failed executions should not generate proofs. - -#[cfg(feature = "risc0")] -fn main() { - use risc0_zkvm::guest::env; - - // Read input bytes from the host - let input_bytes: Vec = env::read(); - - // Execute kernel (agent execution + constraint enforcement) - match kernel_guest::kernel_main(&input_bytes) { - Ok(journal_bytes) => { - // Commit journal to the proof receipt - env::commit_slice(&journal_bytes); - } - Err(error) => { - // Panic aborts proof generation - this is intentional - panic!("Kernel execution failed: {:?}", error); - } - } -} - -#[cfg(not(feature = "risc0"))] -fn main() { - eprintln!("This binary is intended to run inside RISC Zero zkVM."); - eprintln!("Build with --features risc0 and use the methods crate for E2E testing."); - std::process::exit(1); -} \ No newline at end of file diff --git a/crates/constraints/Cargo.toml b/crates/protocol/constraints/Cargo.toml similarity index 100% rename from crates/constraints/Cargo.toml rename to crates/protocol/constraints/Cargo.toml diff --git a/crates/constraints/src/lib.rs b/crates/protocol/constraints/src/lib.rs similarity index 82% rename from crates/constraints/src/lib.rs rename to crates/protocol/constraints/src/lib.rs index 1dfc9af..ba50cd2 100644 --- a/crates/constraints/src/lib.rs +++ b/crates/protocol/constraints/src/lib.rs @@ -15,7 +15,9 @@ use kernel_core::{ /// Echo/test action (TrivialAgent) pub const ACTION_TYPE_ECHO: u32 = 0x00000001; -/// Open a new trading position +/// Open a new trading position (kernel-internal action type) +/// NOTE: This has the same value as ACTION_TYPE_CALL but different semantics. +/// Use ACTION_TYPE_CALL for on-chain vault execution. pub const ACTION_TYPE_OPEN_POSITION: u32 = 0x00000002; /// Close an existing position @@ -27,6 +29,20 @@ pub const ACTION_TYPE_ADJUST_POSITION: u32 = 0x00000004; /// Asset swap/exchange pub const ACTION_TYPE_SWAP: u32 = 0x00000005; +// ============================================================================ +// On-Chain Execution Action Types (matches KernelOutputParser.sol) +// ============================================================================ + +/// CALL action type for on-chain execution via KernelVault +/// Payload: abi.encode(uint256 value, bytes callData) +pub const ACTION_TYPE_CALL: u32 = 0x00000002; + +/// ERC20 transfer action type for on-chain execution +pub const ACTION_TYPE_TRANSFER_ERC20: u32 = 0x00000003; + +/// No-op action type (skipped during execution) +pub const ACTION_TYPE_NO_OP: u32 = 0x00000004; + /// SHA-256 hash of empty AgentOutput encoding [0x00, 0x00, 0x00, 0x00] pub const EMPTY_OUTPUT_COMMITMENT: [u8; 32] = [ 0xdf, 0x3f, 0x61, 0x98, 0x04, 0xa9, 0x2f, 0xdb, @@ -374,14 +390,50 @@ fn validate_action( // Echo action has no specific constraints Ok(()) } - ACTION_TYPE_OPEN_POSITION => { - validate_open_position(action, index, constraint_set) + // ACTION_TYPE_CALL and ACTION_TYPE_OPEN_POSITION share the same value (0x00000002) + // Distinguish by payload size: CALL >= 96 bytes (ABI-encoded), OPEN_POSITION = 45 bytes + ACTION_TYPE_CALL => { + if action.payload.len() >= 96 { + // CALL action (on-chain execution) + validate_call_action(action, index) + } else if action.payload.len() == OpenPositionPayload::SIZE { + // OPEN_POSITION action (kernel internal) + validate_open_position(action, index, constraint_set) + } else { + // Invalid payload size for either type + Err(ConstraintViolation::action( + ConstraintViolationReason::InvalidActionPayload, + index, + )) + } } + // ACTION_TYPE_TRANSFER_ERC20 and ACTION_TYPE_CLOSE_POSITION share value 0x00000003 ACTION_TYPE_CLOSE_POSITION => { - validate_close_position(action, index) + if action.payload.len() == ClosePositionPayload::SIZE { + validate_close_position(action, index) + } else if action.payload.len() == 96 { + // ERC20 transfer (on-chain): abi.encode(address token, address to, uint256 amount) + validate_transfer_erc20_action(action, index) + } else { + Err(ConstraintViolation::action( + ConstraintViolationReason::InvalidActionPayload, + index, + )) + } } + // ACTION_TYPE_NO_OP and ACTION_TYPE_ADJUST_POSITION share value 0x00000004 ACTION_TYPE_ADJUST_POSITION => { - validate_adjust_position(action, index, constraint_set) + if action.payload.is_empty() { + // NO_OP action + Ok(()) + } else if action.payload.len() == AdjustPositionPayload::SIZE { + validate_adjust_position(action, index, constraint_set) + } else { + Err(ConstraintViolation::action( + ConstraintViolationReason::InvalidActionPayload, + index, + )) + } } ACTION_TYPE_SWAP => { validate_swap(action, index, constraint_set) @@ -510,6 +562,97 @@ fn validate_swap( Ok(()) } +/// Validate CALL action (on-chain execution). +/// +/// Payload format: abi.encode(uint256 value, bytes callData) +/// Minimum size: 96 bytes (32 value + 32 offset + 32 length + 0 calldata) +fn validate_call_action(action: &ActionV1, index: usize) -> Result<(), ConstraintViolation> { + // Minimum payload size check + if action.payload.len() < 96 { + return Err(ConstraintViolation::action( + ConstraintViolationReason::InvalidActionPayload, + index, + )); + } + + // Validate target is a valid EVM address (upper 12 bytes must be zero) + if action.target[0..12] != [0u8; 12] { + return Err(ConstraintViolation::action( + ConstraintViolationReason::InvalidActionPayload, + index, + )); + } + + // Basic ABI structure validation: + // bytes 32-63 should contain offset (should be 64 = 0x40) + // bytes 64-95 should contain length of calldata + let offset = u256_from_be_bytes(&action.payload[32..64]); + if offset != 64 { + return Err(ConstraintViolation::action( + ConstraintViolationReason::InvalidActionPayload, + index, + )); + } + + let calldata_len = u256_from_be_bytes(&action.payload[64..96]); + // Verify payload length matches declared calldata length (with 32-byte padding) + let expected_len = 96 + ((calldata_len as usize + 31) / 32) * 32; + if action.payload.len() != expected_len { + return Err(ConstraintViolation::action( + ConstraintViolationReason::InvalidActionPayload, + index, + )); + } + + Ok(()) +} + +/// Validate TRANSFER_ERC20 action (on-chain execution). +/// +/// Payload format: abi.encode(address token, address to, uint256 amount) +/// Size: exactly 96 bytes +fn validate_transfer_erc20_action(action: &ActionV1, index: usize) -> Result<(), ConstraintViolation> { + if action.payload.len() != 96 { + return Err(ConstraintViolation::action( + ConstraintViolationReason::InvalidActionPayload, + index, + )); + } + + // Validate addresses have proper padding (upper 12 bytes should be zero) + // Token address (bytes 0-31) + if action.payload[0..12] != [0u8; 12] { + return Err(ConstraintViolation::action( + ConstraintViolationReason::InvalidActionPayload, + index, + )); + } + + // To address (bytes 32-63) + if action.payload[32..44] != [0u8; 12] { + return Err(ConstraintViolation::action( + ConstraintViolationReason::InvalidActionPayload, + index, + )); + } + + Ok(()) +} + +/// Helper to read a u256 from big-endian bytes (only reads lower 64 bits for practical values) +fn u256_from_be_bytes(bytes: &[u8]) -> u64 { + // For practical values, we only need to check if upper bytes are zero + // and read the lower 8 bytes + if bytes.len() != 32 { + return u64::MAX; // Invalid + } + // Check upper 24 bytes are zero (for values that fit in u64) + if bytes[0..24] != [0u8; 24] { + return u64::MAX; // Value too large + } + u64::from_be_bytes(bytes[24..32].try_into().unwrap()) +} + /// Check if an asset is allowed. /// /// P0.3 single-asset whitelist semantics: diff --git a/crates/kernel-core/Cargo.toml b/crates/protocol/kernel-core/Cargo.toml similarity index 100% rename from crates/kernel-core/Cargo.toml rename to crates/protocol/kernel-core/Cargo.toml diff --git a/crates/kernel-core/src/codec.rs b/crates/protocol/kernel-core/src/codec.rs similarity index 95% rename from crates/kernel-core/src/codec.rs rename to crates/protocol/kernel-core/src/codec.rs index 7a4ac40..41d2a58 100644 --- a/crates/kernel-core/src/codec.rs +++ b/crates/protocol/kernel-core/src/codec.rs @@ -562,11 +562,9 @@ impl CanonicalDecode for ActionV1 { /// This ensures deterministic `action_commitment` regardless of the order /// agents produce actions. /// -/// Ordering: action_type (ascending) → target (lexicographic) → payload (lexicographic) -/// -/// **Important**: This means `AgentOutput` represents an unordered set of actions -/// for commitment purposes. The constraint engine evaluates actions in this -/// canonical sorted order. +/// **Important**: Encoding preserves the agent's action order. The agent is responsible +/// for deterministic ordering of actions. This ensures commitment values are reproducible +/// when the agent produces the same output. impl CanonicalEncode for AgentOutput { fn encoded_len(&self) -> Result { let n = self.actions.len(); @@ -610,16 +608,13 @@ impl CanonicalEncode for AgentOutput { return Err(CodecError::ArithmeticOverflow); } - // Sort actions into canonical order for deterministic encoding - let mut sorted_actions = self.actions.clone(); - sorted_actions.sort(); - + // Preserve agent's action order (agent is responsible for deterministic ordering) // Track starting position to verify total size let start_len = out.len(); put_u32_le(out, n as u32); - for action in &sorted_actions { + for action in &self.actions { let action_len = action.encoded_len()?; if action_len > MAX_SINGLE_ACTION_BYTES { return Err(CodecError::ActionTooLarge { @@ -789,8 +784,8 @@ mod tests { } #[test] - fn test_agent_output_sorted_encoding() { - // Create actions in reverse canonical order + fn test_agent_output_preserves_action_order() { + // Encoding preserves agent's action order - agent is responsible for deterministic ordering. let action_a = ActionV1 { action_type: 0x02, target: [0x11; 32], @@ -808,22 +803,20 @@ mod tests { let encoded = output.encode().unwrap(); - // Verify encoding is sorted by inspecting the first action in the encoded bytes. - // Note: decode does NOT sort - it preserves wire order. Encoding sorts. + // Verify encoding preserves order by inspecting the first action in the encoded bytes. let mut offset = 0; let _count = get_u32_le(&encoded, &mut offset).unwrap(); let first_action_len = get_u32_le(&encoded, &mut offset).unwrap() as usize; let first_action = ActionV1::decode(&encoded[offset..offset + first_action_len]).unwrap(); - // action_b (type 0x01) should be first in canonical order - assert_eq!(first_action.action_type, 0x01); - assert_eq!(first_action.target, [0x22; 32]); + // action_a (type 0x02) should be first since order is preserved + assert_eq!(first_action.action_type, 0x02); + assert_eq!(first_action.target, [0x11; 32]); } #[test] fn test_agent_output_decode_preserves_wire_order() { - // Verify that decode does NOT re-sort - it preserves the order from the wire. - // This is important: canonicalization is an encoding step, not a decoding step. + // Verify that decode preserves the order from the wire. let action_a = ActionV1 { action_type: 0x02, target: [0x11; 32], @@ -839,13 +832,13 @@ mod tests { actions: vec![action_a, action_b], }; - // encode() will sort: [action_b, action_a] + // encode() preserves order let encoded = output.encode().unwrap(); let decoded = AgentOutput::decode(&encoded).unwrap(); - // decoded order matches wire order (which is sorted), not original order + // decoded order matches original order (agent is responsible for deterministic ordering) assert_eq!(decoded.actions.len(), 2); - assert_eq!(decoded.actions[0].action_type, 0x01); // was second, now first after sorting - assert_eq!(decoded.actions[1].action_type, 0x02); + assert_eq!(decoded.actions[0].action_type, 0x02); // first stays first + assert_eq!(decoded.actions[1].action_type, 0x01); // second stays second } } diff --git a/crates/kernel-core/src/hash.rs b/crates/protocol/kernel-core/src/hash.rs similarity index 100% rename from crates/kernel-core/src/hash.rs rename to crates/protocol/kernel-core/src/hash.rs diff --git a/crates/kernel-core/src/lib.rs b/crates/protocol/kernel-core/src/lib.rs similarity index 100% rename from crates/kernel-core/src/lib.rs rename to crates/protocol/kernel-core/src/lib.rs diff --git a/crates/kernel-core/src/types.rs b/crates/protocol/kernel-core/src/types.rs similarity index 100% rename from crates/kernel-core/src/types.rs rename to crates/protocol/kernel-core/src/types.rs diff --git a/crates/runtime/kernel-guest/Cargo.toml b/crates/runtime/kernel-guest/Cargo.toml new file mode 100644 index 0000000..e18f821 --- /dev/null +++ b/crates/runtime/kernel-guest/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "kernel-guest" +version = "0.1.0" +edition = "2021" +description = "Agent-agnostic kernel execution logic for zkVM" +license = "Apache-2.0" + +[dependencies] +kernel-core = { path = "../../protocol/kernel-core", default-features = false } +kernel-sdk = { path = "../../sdk/kernel-sdk" } +constraints = { path = "../../protocol/constraints" } + +# RISC Zero zkVM guest runtime (only needed when building for zkVM) +risc0-zkvm = { version = "3.0", default-features = false, features = ["std"], optional = true } + +[features] +default = [] +risc0 = ["dep:risc0-zkvm"] \ No newline at end of file diff --git a/crates/runtime/kernel-guest/src/lib.rs b/crates/runtime/kernel-guest/src/lib.rs new file mode 100644 index 0000000..a16f35c --- /dev/null +++ b/crates/runtime/kernel-guest/src/lib.rs @@ -0,0 +1,247 @@ +//! Kernel Guest Execution Logic +//! +//! This crate implements the core kernel execution logic that runs inside +//! the zkVM guest. It provides agent-agnostic kernel execution functions +//! that accept injected agent implementations. +//! +//! # Execution Flow +//! +//! 1. Decode input bytes → `KernelInputV1` +//! 2. Validate protocol and kernel versions +//! 3. **Verify agent code hash matches injected agent** (P0.5) +//! 4. Compute input commitment (SHA256) +//! 5. Build `AgentContext` from kernel input +//! 6. Call agent via `AgentEntrypoint` trait +//! 7. Enforce constraints on agent output (UNSKIPPABLE) +//! 8. Compute action commitment (SHA256) +//! 9. Return encoded `KernelJournalV1` +//! +//! # Agent Injection +//! +//! Agents are injected via the [`AgentEntrypoint`] trait. Wrapper crates +//! implement this trait to bind specific agents to the kernel without +//! requiring kernel-guest to have dependencies on individual agent crates. +//! +//! # Agent Code Hash Binding (P0.5) +//! +//! The kernel verifies that `KernelInputV1.agent_code_hash` matches the +//! hash returned by [`AgentEntrypoint::code_hash()`]. This binding ensures: +//! +//! - Proofs are tied to the specific agent implementation that ran +//! - Malicious agent substitution is detected and rejected +//! - Verifiers can trust that the claimed agent actually executed +//! +//! If the hash doesn't match, `KernelError::AgentCodeHashMismatch` is returned. + +use kernel_core::*; +use kernel_sdk::agent::AgentContext; +use constraints::{enforce_constraints, ConstraintSetV1, EMPTY_OUTPUT_COMMITMENT}; + +// Re-export KernelError for wrapper crates to use. +pub use kernel_core::KernelError; + +// ============================================================================ +// Agent Entrypoint Trait +// ============================================================================ + +/// Trait for injecting agent implementations into the kernel. +/// +/// Wrapper crates implement this trait to bind a specific agent to the kernel +/// without requiring kernel-guest to have compile-time dependencies on +/// individual agent crates. +/// +/// # Example +/// +/// ```ignore +/// use kernel_guest::AgentEntrypoint; +/// use kernel_sdk::agent::AgentContext; +/// use kernel_core::AgentOutput; +/// +/// pub struct MyAgentWrapper; +/// +/// impl AgentEntrypoint for MyAgentWrapper { +/// fn code_hash(&self) -> [u8; 32] { +/// my_agent::AGENT_CODE_HASH +/// } +/// +/// fn run(&self, ctx: &AgentContext, opaque_inputs: &[u8]) -> AgentOutput { +/// my_agent::agent_main(ctx, opaque_inputs) +/// } +/// } +/// ``` +pub trait AgentEntrypoint { + /// Returns the agent's code hash (computed at build time). + /// + /// This hash uniquely identifies the agent implementation and is + /// verified against `KernelInputV1.agent_code_hash` to ensure the + /// proof is bound to the correct agent code. + fn code_hash(&self) -> [u8; 32]; + + /// Executes the agent logic. + /// + /// # Arguments + /// + /// * `ctx` - Execution context with identity and metadata + /// * `opaque_inputs` - Agent-specific input data (max 64,000 bytes) + /// + /// # Returns + /// + /// `AgentOutput` containing ordered actions to be executed. + fn run(&self, ctx: &AgentContext, opaque_inputs: &[u8]) -> AgentOutput; +} + +// ============================================================================ +// Agent-Injected Kernel Execution (Primary API) +// ============================================================================ + +/// Execute kernel with an injected agent implementation. +/// +/// This is the primary API for kernel execution. The agent is provided via +/// the [`AgentEntrypoint`] trait, allowing the kernel to be agent-agnostic. +/// +/// # Agent Code Hash Binding (P0.5) +/// +/// Step 3 verifies that `KernelInputV1.agent_code_hash` matches +/// `agent.code_hash()`. This ensures proofs are cryptographically bound +/// to the specific agent code that executed. +/// +/// # Constraint Enforcement (P0.3) +/// +/// Constraints are ALWAYS enforced after agent execution. If any constraint +/// is violated: +/// - `execution_status` is set to `Failure` (0x02) +/// - `action_commitment` is computed over an empty `AgentOutput` +/// - A valid journal is still produced +/// +/// # Arguments +/// +/// * `input_bytes` - Canonical encoding of KernelInputV1 +/// * `agent` - Agent implementation (provides code hash and execution logic) +/// +/// # Returns +/// +/// * `Ok(Vec)` - Canonical encoding of KernelJournalV1 (always produced) +/// * `Err(KernelError)` - Critical failure (decoding, version mismatch, hash mismatch) +/// +/// # Determinism +/// +/// This function is fully deterministic. Same input bytes and agent will +/// always produce the same output bytes. +pub fn kernel_main_with_agent( + input_bytes: &[u8], + agent: &dyn AgentEntrypoint, +) -> Result, KernelError> { + kernel_main_with_agent_and_constraints(input_bytes, agent, &ConstraintSetV1::default()) +} + +/// Execute kernel with an injected agent and custom constraint set. +/// +/// This variant allows specifying a custom constraint set instead of +/// using the default. Useful for testing and specialized deployments. +/// +/// # Arguments +/// +/// * `input_bytes` - Canonical encoding of KernelInputV1 +/// * `agent` - Agent implementation (provides code hash and execution logic) +/// * `constraint_set` - Custom constraint set to enforce +/// +/// # Returns +/// +/// * `Ok(Vec)` - Canonical encoding of KernelJournalV1 (always produced) +/// * `Err(KernelError)` - Critical failure (decoding, version mismatch, hash mismatch) +pub fn kernel_main_with_agent_and_constraints( + input_bytes: &[u8], + agent: &dyn AgentEntrypoint, + constraint_set: &ConstraintSetV1, +) -> Result, KernelError> { + // 1. Decode input + let input = KernelInputV1::decode(input_bytes)?; + + // 2. Validate versions (already checked in decode, but be explicit) + if input.protocol_version != PROTOCOL_VERSION { + return Err(KernelError::UnsupportedProtocolVersion { + expected: PROTOCOL_VERSION, + actual: input.protocol_version, + }); + } + + if input.kernel_version != KERNEL_VERSION { + return Err(KernelError::UnsupportedKernelVersion { + expected: KERNEL_VERSION, + actual: input.kernel_version, + }); + } + + // 3. Verify agent code hash matches injected agent (P0.5 binding) + // + // This check ensures that the agent_code_hash declared in the input + // matches the actual agent implementation. Without this, a malicious + // prover could claim they ran agent X but actually run agent Y. + if input.agent_code_hash != agent.code_hash() { + return Err(KernelError::AgentCodeHashMismatch); + } + + // 4. Compute input commitment (over full input bytes) + let input_commitment = compute_input_commitment(input_bytes); + + // 5. Build agent context from input (using kernel-sdk AgentContext) + let agent_ctx = AgentContext::new( + input.protocol_version, + input.kernel_version, + input.agent_id, + input.agent_code_hash, + input.constraint_set_hash, + input.input_root, + input.execution_nonce, + ); + + // 6. Execute agent via injected implementation + let agent_output = agent.run(&agent_ctx, &input.opaque_agent_inputs); + + // 7. ENFORCE CONSTRAINTS (UNSKIPPABLE) + // This is the critical safety check that validates all agent actions. + let (validated_output, execution_status) = + match enforce_constraints(&input, &agent_output, constraint_set) { + Ok(validated) => { + // Constraints passed - use validated output + (validated, ExecutionStatus::Success) + } + Err(_violation) => { + // Constraints violated - use empty output and Failure status + // The violation details are not included in the journal for P0.3 + // but could be logged or added in future versions. + (AgentOutput { actions: vec![] }, ExecutionStatus::Failure) + } + }; + + // 8. Compute action commitment + // On Success: computed over validated output + // On Failure: computed over empty output (deterministic constant) + let action_commitment = if execution_status == ExecutionStatus::Success { + let output_bytes = validated_output + .encode() + .map_err(KernelError::EncodingFailed)?; + compute_action_commitment(&output_bytes) + } else { + // Use pre-computed constant for empty output commitment + EMPTY_OUTPUT_COMMITMENT + }; + + // 9. Construct journal with all identity and commitment fields + let journal = KernelJournalV1 { + protocol_version: PROTOCOL_VERSION, + kernel_version: KERNEL_VERSION, + agent_id: input.agent_id, + agent_code_hash: input.agent_code_hash, + constraint_set_hash: input.constraint_set_hash, + input_root: input.input_root, + execution_nonce: input.execution_nonce, + input_commitment, + action_commitment, + execution_status, + }; + + // 10. Encode and return journal (always produced) + journal.encode().map_err(KernelError::EncodingFailed) +} + diff --git a/crates/methods/Cargo.toml b/crates/runtime/risc0-methods/Cargo.toml similarity index 94% rename from crates/methods/Cargo.toml rename to crates/runtime/risc0-methods/Cargo.toml index 6ab265d..8cb6083 100644 --- a/crates/methods/Cargo.toml +++ b/crates/runtime/risc0-methods/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "methods" +name = "risc0-methods" version = "0.1.0" edition = "2021" description = "RISC Zero methods crate - builds kernel-guest as zkVM guest and exports ELF/IMAGE_ID" diff --git a/crates/methods/build.rs b/crates/runtime/risc0-methods/build.rs similarity index 64% rename from crates/methods/build.rs rename to crates/runtime/risc0-methods/build.rs index 873ab0d..403ca52 100644 --- a/crates/methods/build.rs +++ b/crates/runtime/risc0-methods/build.rs @@ -1,10 +1,10 @@ -//! Build script for the methods crate. +//! Build script for the risc0-methods crate. //! //! This compiles zkvm-guest as a RISC Zero guest program and generates //! Rust source code with the ELF binary and IMAGE_ID embedded. //! -//! zkvm-guest is a thin wrapper around kernel-guest that provides the -//! main() entry point for the zkVM execution environment. +//! zkvm-guest is a thin wrapper that uses a binding crate to bind +//! a specific agent to the kernel-guest library. //! //! The generated code exports: //! - `ZKVM_GUEST_ELF`: The compiled guest ELF binary @@ -22,10 +22,11 @@ fn main() { // Get the manifest directory let manifest_dir = PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap()); - // Paths to rebuild triggers + // Paths to rebuild triggers (relative to crates/runtime/risc0-methods) let zkvm_guest_dir = manifest_dir.join("zkvm-guest"); - let kernel_guest_dir = manifest_dir.join("..").join("kernel-guest"); - let example_agent_dir = manifest_dir.join("..").join("example-agent"); + let kernel_guest_dir = manifest_dir.join("../kernel-guest"); + let wrapper_dir = manifest_dir.join("../../agents/wrappers/kernel-guest-binding-yield"); + let yield_agent_dir = manifest_dir.join("../../agents/examples/example-yield-agent"); // Rebuild if the zkvm-guest wrapper changes println!( @@ -47,14 +48,24 @@ fn main() { kernel_guest_dir.join("Cargo.toml").display() ); - // Rebuild if example-agent changes + // Rebuild if the binding crate changes println!( "cargo:rerun-if-changed={}", - example_agent_dir.join("src").display() + wrapper_dir.join("src").display() ); println!( "cargo:rerun-if-changed={}", - example_agent_dir.join("Cargo.toml").display() + wrapper_dir.join("Cargo.toml").display() + ); + + // Rebuild if the yield agent changes + println!( + "cargo:rerun-if-changed={}", + yield_agent_dir.join("src").display() + ); + println!( + "cargo:rerun-if-changed={}", + yield_agent_dir.join("Cargo.toml").display() ); println!("cargo:rerun-if-changed=build.rs"); diff --git a/crates/methods/src/lib.rs b/crates/runtime/risc0-methods/src/lib.rs similarity index 96% rename from crates/methods/src/lib.rs rename to crates/runtime/risc0-methods/src/lib.rs index d0b6306..ff7c6f1 100644 --- a/crates/methods/src/lib.rs +++ b/crates/runtime/risc0-methods/src/lib.rs @@ -11,7 +11,7 @@ //! # Usage //! //! ```ignore -//! use methods::{ZKVM_GUEST_ELF, ZKVM_GUEST_ID}; +//! use risc0_methods::{ZKVM_GUEST_ELF, ZKVM_GUEST_ID}; //! use risc0_zkvm::{default_prover, ExecutorEnv}; //! //! let env = ExecutorEnv::builder() diff --git a/crates/methods/zkvm-guest/Cargo.lock b/crates/runtime/risc0-methods/zkvm-guest/Cargo.lock similarity index 99% rename from crates/methods/zkvm-guest/Cargo.lock rename to crates/runtime/risc0-methods/zkvm-guest/Cargo.lock index 5fe291c..3ca2389 100644 --- a/crates/methods/zkvm-guest/Cargo.lock +++ b/crates/runtime/risc0-methods/zkvm-guest/Cargo.lock @@ -537,7 +537,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] -name = "example-agent" +name = "example-yield-agent" version = "0.1.0" dependencies = [ "kernel-sdk", @@ -683,11 +683,21 @@ name = "kernel-guest" version = "0.1.0" dependencies = [ "constraints", - "example-agent", "kernel-core", "kernel-sdk", ] +[[package]] +name = "kernel-guest-binding-yield" +version = "0.1.0" +dependencies = [ + "constraints", + "example-yield-agent", + "kernel-core", + "kernel-guest", + "kernel-sdk", +] + [[package]] name = "kernel-sdk" version = "0.1.0" @@ -1501,6 +1511,6 @@ dependencies = [ name = "zkvm-guest" version = "0.1.0" dependencies = [ - "kernel-guest", + "kernel-guest-binding-yield", "risc0-zkvm", ] diff --git a/crates/methods/zkvm-guest/Cargo.toml b/crates/runtime/risc0-methods/zkvm-guest/Cargo.toml similarity index 64% rename from crates/methods/zkvm-guest/Cargo.toml rename to crates/runtime/risc0-methods/zkvm-guest/Cargo.toml index 4f06828..933c973 100644 --- a/crates/methods/zkvm-guest/Cargo.toml +++ b/crates/runtime/risc0-methods/zkvm-guest/Cargo.toml @@ -2,7 +2,7 @@ # # This is a minimal wrapper crate that allows risc0-build to compile # kernel-guest as a zkVM guest program. It provides the main() entry point -# that delegates to the kernel-guest library. +# that delegates to the wrapper crate's kernel_main function. [package] name = "zkvm-guest" @@ -13,8 +13,9 @@ edition = "2021" [workspace] [dependencies] -# The actual kernel-guest library with all its dependencies -kernel-guest = { path = "../../kernel-guest", default-features = true } +# Wrapper crate that binds the yield agent to kernel-guest. +# To use a different agent, change this to a different wrapper crate. +kernel-guest-binding-yield = { path = "../../../agents/wrappers/kernel-guest-binding-yield" } # RISC Zero zkVM guest runtime risc0-zkvm = { version = "3.0", default-features = false, features = ["std"] } diff --git a/crates/methods/zkvm-guest/src/main.rs b/crates/runtime/risc0-methods/zkvm-guest/src/main.rs similarity index 75% rename from crates/methods/zkvm-guest/src/main.rs rename to crates/runtime/risc0-methods/zkvm-guest/src/main.rs index 7dafda6..be6fb73 100644 --- a/crates/methods/zkvm-guest/src/main.rs +++ b/crates/runtime/risc0-methods/zkvm-guest/src/main.rs @@ -1,6 +1,7 @@ //! RISC Zero zkVM Guest Entry Point //! -//! This is a wrapper that delegates to the kernel-guest library for zkVM execution. +//! This is a wrapper that delegates to the wrapper crate for zkVM execution. +//! The wrapper crate binds a specific agent to the kernel-guest library. //! //! # Execution Flow //! @@ -19,8 +20,8 @@ fn main() { // Read input bytes from the host let input_bytes: Vec = env::read(); - // Execute kernel (agent execution + constraint enforcement) - match kernel_guest::kernel_main(&input_bytes) { + // Execute kernel via the wrapper crate (which binds the specific agent) + match kernel_guest_binding_yield::kernel_main(&input_bytes) { Ok(journal_bytes) => { // Commit journal to the proof receipt env::commit_slice(&journal_bytes); diff --git a/crates/kernel-sdk/Cargo.toml b/crates/sdk/kernel-sdk/Cargo.toml similarity index 87% rename from crates/kernel-sdk/Cargo.toml rename to crates/sdk/kernel-sdk/Cargo.toml index 3755ef1..fe82abf 100644 --- a/crates/kernel-sdk/Cargo.toml +++ b/crates/sdk/kernel-sdk/Cargo.toml @@ -13,7 +13,7 @@ license = "Apache-2.0" crate-type = ["rlib"] [dependencies] -kernel-core = { path = "../kernel-core", default-features = false } +kernel-core = { path = "../../protocol/kernel-core", default-features = false } [dev-dependencies] # No dev dependencies - keep SDK minimal diff --git a/crates/kernel-sdk/examples/echo_agent.rs b/crates/sdk/kernel-sdk/examples/echo_agent.rs similarity index 100% rename from crates/kernel-sdk/examples/echo_agent.rs rename to crates/sdk/kernel-sdk/examples/echo_agent.rs diff --git a/crates/kernel-sdk/src/agent.rs b/crates/sdk/kernel-sdk/src/agent.rs similarity index 100% rename from crates/kernel-sdk/src/agent.rs rename to crates/sdk/kernel-sdk/src/agent.rs diff --git a/crates/kernel-sdk/src/bytes.rs b/crates/sdk/kernel-sdk/src/bytes.rs similarity index 100% rename from crates/kernel-sdk/src/bytes.rs rename to crates/sdk/kernel-sdk/src/bytes.rs diff --git a/crates/kernel-sdk/src/lib.rs b/crates/sdk/kernel-sdk/src/lib.rs similarity index 97% rename from crates/kernel-sdk/src/lib.rs rename to crates/sdk/kernel-sdk/src/lib.rs index 4d15d82..28831e5 100644 --- a/crates/kernel-sdk/src/lib.rs +++ b/crates/sdk/kernel-sdk/src/lib.rs @@ -130,6 +130,10 @@ pub mod prelude { ACTION_TYPE_CLOSE_POSITION, ACTION_TYPE_ADJUST_POSITION, ACTION_TYPE_SWAP, + // On-chain execution action types + ACTION_TYPE_CALL, + ACTION_TYPE_TRANSFER_ERC20, + ACTION_TYPE_NO_OP, }; // Action constructors @@ -139,6 +143,9 @@ pub mod prelude { close_position_action, adjust_position_action, swap_action, + // On-chain execution helpers + call_action, + address_to_bytes32, }; // Payload decode helpers + types diff --git a/crates/kernel-sdk/src/math.rs b/crates/sdk/kernel-sdk/src/math.rs similarity index 100% rename from crates/kernel-sdk/src/math.rs rename to crates/sdk/kernel-sdk/src/math.rs diff --git a/crates/kernel-sdk/src/types.rs b/crates/sdk/kernel-sdk/src/types.rs similarity index 81% rename from crates/kernel-sdk/src/types.rs rename to crates/sdk/kernel-sdk/src/types.rs index 81734aa..69595d2 100644 --- a/crates/kernel-sdk/src/types.rs +++ b/crates/sdk/kernel-sdk/src/types.rs @@ -76,6 +76,42 @@ pub const ACTION_TYPE_ADJUST_POSITION: u32 = 0x00000004; /// - `amount`: u64 - Amount to swap (little-endian) pub const ACTION_TYPE_SWAP: u32 = 0x00000005; +// ============================================================================ +// On-Chain Execution Action Types +// ============================================================================ +// +// These action types are used for on-chain vault execution via KernelVault.sol. +// They map to the action type constants in KernelOutputParser.sol. + +/// CALL action type for on-chain execution (0x00000002). +/// +/// Used by KernelVault.execute() to perform arbitrary contract calls. +/// Matches KernelOutputParser.sol ACTION_TYPE_CALL. +/// +/// Payload schema (ABI-encoded): +/// - `abi.encode(uint256 value, bytes callData)` +/// +/// On-chain execution: `target.call{value: value}(callData)` +/// +/// # Target Format +/// +/// The target is a bytes32 with the EVM address left-padded: +/// - Upper 12 bytes: 0x00 (must be zero for valid EVM address) +/// - Lower 20 bytes: EVM address +pub const ACTION_TYPE_CALL: u32 = 0x00000002; + +/// ERC20 transfer action type for on-chain execution (0x00000003). +/// +/// Used by KernelVault.execute() to transfer ERC20 tokens. +/// Matches KernelOutputParser.sol ACTION_TYPE_TRANSFER_ERC20. +pub const ACTION_TYPE_TRANSFER_ERC20: u32 = 0x00000003; + +/// No-op action type (0x00000004). +/// +/// Used for testing or placeholder actions that should be skipped. +/// Matches KernelOutputParser.sol ACTION_TYPE_NO_OP. +pub const ACTION_TYPE_NO_OP: u32 = 0x00000004; + // ============================================================================ // Payload Size Constants // ============================================================================ @@ -223,6 +259,88 @@ pub fn swap_action( } } +/// Create a CALL action for on-chain execution. +/// +/// This creates an action that will be executed by KernelVault.execute() +/// as: `target.call{value: value}(call_data)` +/// +/// # Arguments +/// * `target` - 32-byte target (EVM address left-padded with 12 zero bytes) +/// * `value` - ETH value to send (in wei) +/// * `call_data` - Raw calldata bytes (function selector + encoded args) +/// +/// # Payload Format +/// +/// The payload is ABI-encoded as: `abi.encode(uint256 value, bytes callData)` +/// - bytes 0-31: value as uint256 (big-endian) +/// - bytes 32-63: offset to bytes data (always 64) +/// - bytes 64-95: length of callData +/// - bytes 96+: callData (padded to 32-byte boundary) +#[inline] +#[must_use] +pub fn call_action(target: [u8; 32], value: u128, call_data: &[u8]) -> ActionV1 { + ActionV1 { + action_type: ACTION_TYPE_CALL, + target, + payload: encode_call_payload(value, call_data), + } +} + +/// Convert a 20-byte EVM address to bytes32 (left-padded). +/// +/// The resulting bytes32 has: +/// - Upper 12 bytes: 0x00 +/// - Lower 20 bytes: The EVM address +#[inline] +#[must_use] +pub fn address_to_bytes32(addr: &[u8; 20]) -> [u8; 32] { + let mut result = [0u8; 32]; + result[12..32].copy_from_slice(addr); + result +} + +/// Encode a u256 value in big-endian format (for ABI encoding). +#[inline] +fn encode_u256_be(value: u128) -> [u8; 32] { + let mut result = [0u8; 32]; + // Value fits in u128, so only fill lower 16 bytes (big-endian) + result[16..32].copy_from_slice(&value.to_be_bytes()); + result +} + +/// Encode the CALL payload: abi.encode(uint256 value, bytes callData) +/// +/// ABI encoding for (uint256, bytes): +/// - bytes 0-31: value (uint256, big-endian) +/// - bytes 32-63: offset to bytes data (always 64 = 0x40) +/// - bytes 64-95: length of bytes data +/// - bytes 96+: bytes data (padded to 32-byte boundary) +fn encode_call_payload(value: u128, call_data: &[u8]) -> Vec { + let data_len = call_data.len(); + // Pad data to 32-byte boundary + let padded_len = ((data_len + 31) / 32) * 32; + + // Total size: 32 (value) + 32 (offset) + 32 (length) + padded_data + let total_size = 96 + padded_len; + let mut payload = Vec::with_capacity(total_size); + + // 1. uint256 value + payload.extend_from_slice(&encode_u256_be(value)); + + // 2. offset to bytes data (always 64 = 0x40) + payload.extend_from_slice(&encode_u256_be(64)); + + // 3. length of bytes data + payload.extend_from_slice(&encode_u256_be(data_len as u128)); + + // 4. bytes data (padded) + payload.extend_from_slice(call_data); + // Pad to 32-byte boundary + payload.resize(total_size, 0); + + payload +} + // ============================================================================ // Payload Decode Helpers // ============================================================================ diff --git a/crates/testing/e2e-tests/Cargo.toml b/crates/testing/e2e-tests/Cargo.toml new file mode 100644 index 0000000..d507579 --- /dev/null +++ b/crates/testing/e2e-tests/Cargo.toml @@ -0,0 +1,56 @@ +[package] +name = "e2e-tests" +version = "0.1.0" +edition = "2021" +description = "End-to-end zkVM proof tests for the execution kernel" +license = "Apache-2.0" +publish = false + +# This crate contains only tests, no library +[lib] +path = "src/lib.rs" + +[dependencies] +# Kernel crates for types and encoding +kernel-core = { path = "../../protocol/kernel-core" } +kernel-sdk = { path = "../../sdk/kernel-sdk" } +constraints = { path = "../../protocol/constraints" } + +# Agent code hash for input construction +example-yield-agent = { path = "../../agents/examples/example-yield-agent" } + +# Methods crate provides the guest ELF and IMAGE_ID +risc0-methods = { path = "../../runtime/risc0-methods", optional = true } + +# RISC Zero prover and verifier (host-side) +risc0-zkvm = { version = "3.0", default-features = false, optional = true } + +# On-chain interaction dependencies (for phase3-e2e) +alloy = { version = "0.8", features = ["full"], optional = true } +tokio = { version = "1", features = ["full"], optional = true } +dotenvy = { version = "0.15", optional = true } + +[dev-dependencies] +# Same dependencies for tests +kernel-core = { path = "../../protocol/kernel-core" } +kernel-sdk = { path = "../../sdk/kernel-sdk" } +constraints = { path = "../../protocol/constraints" } +example-yield-agent = { path = "../../agents/examples/example-yield-agent" } + +[dependencies.hex] +version = "0.4" + +[features] +default = [] +# Enable this feature to run zkVM proof tests +# Requires RISC Zero toolchain installed +risc0-e2e = ["dep:risc0-methods", "dep:risc0-zkvm", "risc0-zkvm/prove"] + +# Enable this feature to run Phase 3 E2E yield tests +# Requires RISC Zero toolchain + deployed contracts +phase3-e2e = [ + "dep:alloy", + "dep:tokio", + "dep:dotenvy", + "risc0-e2e", +] diff --git a/crates/testing/e2e-tests/README.md b/crates/testing/e2e-tests/README.md new file mode 100644 index 0000000..e8cbf2e --- /dev/null +++ b/crates/testing/e2e-tests/README.md @@ -0,0 +1,334 @@ +# E2E zkVM Proof Tests + +End-to-end integration tests that verify the complete execution kernel flow using RISC Zero zkVM proofs, including on-chain verification and execution. + +## Overview + +These tests verify: + +1. **Agent → Guest → Proof → Verification** pipeline works correctly +2. **Agent code hash binding** prevents unauthorized agent substitution +3. **Determinism** - same input always produces same output +4. **Commitment integrity** - input and action commitments are correctly computed +5. **On-chain execution** - proofs verify on-chain and trigger vault actions + +## Prerequisites + +### Install RISC Zero Toolchain + +```bash +# Install cargo-risczero +cargo install cargo-risczero + +# Install the RISC Zero toolchain (includes riscv32im target) +cargo risczero install +``` + +### Verify Installation + +```bash +cargo risczero --version +``` + +### Install Foundry (for on-chain tests) + +```bash +curl -L https://foundry.paradigm.xyz | bash +foundryup +``` + +## Running Tests + +### Unit Tests (no zkVM required) + +```bash +# Run unit tests without proof generation +cargo test -p e2e-tests +``` + +### Full E2E Proof Tests (off-chain) + +```bash +# Run with zkVM proof generation (requires RISC Zero toolchain) +cargo test -p e2e-tests --features risc0-e2e -- --nocapture +``` + +### Phase 3 Yield Agent Tests + +```bash +# Run yield agent proof tests +cargo test -p e2e-tests --features phase3-e2e -- --nocapture +``` + +## Test Cases + +### 1. `test_e2e_success_with_yield_agent` + +Verifies the happy path with the yield agent: +- Valid 48-byte input (vault address + yield source address + amount) +- Proof generation succeeds +- Receipt verifies against IMAGE_ID +- Journal contains correct: + - `execution_status == Success` + - `input_commitment == SHA256(input_bytes)` + - `action_commitment` matches expected yield agent output (2 CALL actions) + +### 2. `test_e2e_agent_code_hash_mismatch` + +Verifies security: +- Input with wrong `agent_code_hash` (all zeros) +- Guest execution fails with `AgentCodeHashMismatch` +- No valid proof/receipt is produced + +### 3. `test_e2e_empty_output_invalid_input_size` + +Verifies empty output handling: +- Input with wrong size (not 48 bytes for yield agent) +- Proof generation succeeds (empty output is valid) +- `action_commitment == EMPTY_OUTPUT_COMMITMENT` + +### 4. `test_e2e_determinism` + +Verifies deterministic execution: +- Same input run twice +- Both runs produce identical journal bytes + +--- + +## On-Chain E2E Test: `test_full_e2e_yield_execution` + +This is the complete end-to-end test that generates a zkVM proof and submits it to a deployed smart contract on Sepolia testnet. + +### What It Tests + +The test verifies the full yield farming flow: + +``` +Vault (has ETH) + │ + ▼ zkVM generates proof of agent execution +Agent produces 2 actions: + 1. CALL: deposit ETH to MockYieldSource + 2. CALL: withdraw ETH + 10% yield from MockYieldSource + │ + ▼ Submit proof to vault.execute() +On-chain verifier validates RISC Zero proof + │ + ▼ Vault executes agent's actions +MockYieldSource receives deposit, returns deposit + 10% yield + │ + ▼ +Vault balance increases by 10% of transfer amount +``` + +### Deployed Contracts (Sepolia) + +The following contracts are already deployed and ready to use: + +| Contract | Address | +|----------|---------| +| KernelExecutionVerifier | `0x9Ef5bAB590AFdE8036D57b89ccD2947D4E3b1EFA` | +| KernelVault | `0xAdeDA97D2D07C7f2e332fD58F40Eb4f7F0192be7` | +| MockYieldSource | `0x7B35E3F2e810170f146d31b00262b9D7138F9b39` | +| RISC Zero Verifier Router | `0x925d8331ddc0a1F0d96E68CF073DFE1d92b69187` | + +### Funding Requirements + +Before running the test, ensure both contracts have sufficient Sepolia ETH: + +1. **KernelVault** - Needs ETH to transfer to the yield source +2. **MockYieldSource** - Needs ETH reserves to pay the 10% yield + +```bash +# Check current balances +cast balance 0xAdeDA97D2D07C7f2e332fD58F40Eb4f7F0192be7 --rpc-url $RPC_URL # Vault +cast balance 0x7B35E3F2e810170f146d31b00262b9D7138F9b39 --rpc-url $RPC_URL # MockYieldSource + +# Fund the vault (if needed) +cast send 0xAdeDA97D2D07C7f2e332fD58F40Eb4f7F0192be7 \ + --value 0.5ether \ + --private-key $PRIVATE_KEY --rpc-url $RPC_URL + +# Fund the MockYieldSource (if needed - must have enough to pay 10% yield) +cast send 0x7B35E3F2e810170f146d31b00262b9D7138F9b39 \ + --value 1ether \ + --private-key $PRIVATE_KEY --rpc-url $RPC_URL +``` + +**Example**: If you transfer 0.1 ETH, the MockYieldSource needs at least 0.01 ETH in reserves to pay the 10% yield. + +### Running the On-Chain Test + +```bash +# Set all required environment variables +export VAULT_ADDRESS=0xAdeDA97D2D07C7f2e332fD58F40Eb4f7F0192be7 +export MOCK_YIELD_ADDRESS=0x7B35E3F2e810170f146d31b00262b9D7138F9b39 +export RPC_URL=https://eth-sepolia.g.alchemy.com/v2/YOUR_KEY +export PRIVATE_KEY=0x... +export EXECUTION_NONCE=1 # Must match vault's lastExecutionNonce + 1 +export TRANSFER_AMOUNT=10000000000000000 # 0.01 ETH in wei + +# Run the test +cargo test --release -p e2e-tests --features phase3-e2e \ + test_full_e2e_yield_execution -- --ignored --nocapture +``` + +### Expected Output + +``` +=== Initial State === +Vault address: 0xAdeDA97D2D07C7f2e332fD58F40Eb4f7F0192be7 +MockYieldSource address: 0x7B35E3F2e810170f146d31b00262b9D7138F9b39 +Initial nonce: 1 +Initial vault balance: 1010000000000000000 wei +Agent ID: 0x0000000000000000000000000000000000000000000000000000000000000001 +Transfer amount: 100000000000000000 wei + +=== Generating zkVM Proof === +Proof generated and verified! + +=== Submitting Transaction === +Journal length: 209 bytes +Seal length: 260 bytes +Agent output length: 348 bytes +Transaction sent: 0x376a678046d5166c0f712ab90afe85d769a44c11e4d919d5f6e8ad776c7a8cac +Transaction confirmed in block: Some(10141050) +Gas used: 403945 + +=== Verifying Results === +Final nonce: 2 +Final vault balance: 1020000000000000000 wei +MockYieldSource deposits[vault]: 0 wei + +=== E2E Test Passed! === +Yield earned: 10000000000000000 wei (10%) +``` + +### Checking Current Vault State + +Before running the test, check the vault state: + +```bash +# Get current nonce (use nonce + 1 for EXECUTION_NONCE) +cast call $VAULT_ADDRESS "lastExecutionNonce()(uint64)" --rpc-url $RPC_URL + +# Get vault balance +cast balance $VAULT_ADDRESS --rpc-url $RPC_URL + +# Get MockYieldSource balance (needs enough for yield) +cast balance $MOCK_YIELD_ADDRESS --rpc-url $RPC_URL +``` + +### Troubleshooting On-Chain Tests + +#### "execution reverted: Invalid proof" + +- Ensure the IMAGE_ID is registered with the verifier +- Verify the IMAGE_ID matches the built kernel-guest + +```bash +# Check if agent is registered +cast call $VERIFIER_ADDRESS "registeredImageIds(bytes32)(bytes32)" $AGENT_ID --rpc-url $RPC_URL +``` + +#### "execution reverted: Invalid nonce" + +- Check the current nonce and use the next one: + +```bash +cast call $VAULT_ADDRESS "lastExecutionNonce()(uint64)" --rpc-url $RPC_URL +``` + +#### "execution reverted: Insufficient balance" + +- Ensure the vault has enough ETH for the transfer +- Ensure MockYieldSource has enough ETH to pay the 10% yield + +--- + +## CI Integration + +These tests are **feature-gated** to avoid requiring RISC Zero in all CI environments: + +```yaml +# In CI, only run E2E tests in environments with RISC Zero installed +- name: Run E2E proof tests + if: ${{ matrix.risc0-enabled }} + run: cargo test -p e2e-tests --features risc0-e2e +``` + +For CI without RISC Zero: + +```yaml +# Unit tests always work +- name: Run unit tests + run: cargo test -p e2e-tests +``` + +## Reproducible Builds + +For deterministic guest ELF builds (useful for IMAGE_ID reproducibility): + +```bash +RISC0_USE_DOCKER=1 cargo test -p e2e-tests --features risc0-e2e +``` + +This requires Docker and uses the official RISC Zero Docker image. + +## Architecture + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ e2e-tests │ +│ │ +│ ┌─────────────────────────────────────────────────────────────────────┐ │ +│ │ Off-Chain (zkVM Proof) │ │ +│ │ ┌────────────────┐ ┌────────────────┐ ┌────────────────┐ │ │ +│ │ │ Test Input │──▶│ risc0-methods │──▶│ Prover │ │ │ +│ │ │ (KernelInputV1)│ │ (ELF+IMAGE_ID) │ │ (risc0-zkvm) │ │ │ +│ │ └────────────────┘ └────────────────┘ └───────┬────────┘ │ │ +│ │ │ │ │ +│ │ ┌────────▼────────┐ │ │ +│ │ │ Receipt │ │ │ +│ │ │ (Seal+Journal) │ │ │ +│ │ └────────┬────────┘ │ │ +│ └─────────────────────────────────────────────────────┼────────────────┘ │ +│ │ │ +│ ┌─────────────────────────────────────────────────────┼────────────────┐ │ +│ │ On-Chain (Sepolia) │ │ │ +│ │ ┌────────▼────────┐ │ │ +│ │ ┌────────────────┐ │ KernelVault │ │ │ +│ │ │ RISC Zero │◀───────────────────────│ .execute() │ │ │ +│ │ │ Verifier │ verify proof └────────┬────────┘ │ │ +│ │ └────────────────┘ │ │ │ +│ │ │ execute actions│ │ +│ │ ┌────────▼────────┐ │ │ +│ │ │ MockYieldSource │ │ │ +│ │ │ (deposit+yield) │ │ │ +│ │ └─────────────────┘ │ │ +│ └──────────────────────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────────────────────┘ +``` + +## Agent Registration Values + +| Field | Value | +|-------|-------| +| IMAGE_ID | `0x5f42241afd61bf9e341442c8baffa9c544cf20253720f2540cf6705f27bae2c4` | +| AGENT_CODE_HASH | `0x5aac6b1fedf1b0c0ccc037c3223b7b5c8b679f48b9c599336c0dc777be88924b` | +| AGENT_ID | `0x0000000000000000000000000000000000000000000000000000000000000001` | + +## Files + +- `src/lib.rs` - Test implementations and helper functions +- `src/phase3_yield.rs` - Yield agent tests and on-chain E2E test +- `Cargo.toml` - Dependencies with feature gates +- `README.md` - This file + +## Related Crates + +- `risc0-methods` - Builds kernel-guest as RISC Zero guest, exports ELF/IMAGE_ID +- `kernel-guest` - The guest program that runs in zkVM (agent-agnostic) +- `kernel-guest-binding-yield` - Binds yield agent to kernel-guest +- `example-yield-agent` - Provides `agent_main` and `AGENT_CODE_HASH` +- `kernel-core` - Types and encoding used by both host and guest diff --git a/crates/testing/e2e-tests/src/bin/print_registration_info.rs b/crates/testing/e2e-tests/src/bin/print_registration_info.rs new file mode 100644 index 0000000..aefd9fe --- /dev/null +++ b/crates/testing/e2e-tests/src/bin/print_registration_info.rs @@ -0,0 +1,65 @@ +//! Print registration info for on-chain deployment +//! +//! Run with: +//! ```bash +//! cargo run -p e2e-tests --bin print_registration_info --features phase3-e2e +//! ``` + +#[cfg(not(feature = "phase3-e2e"))] +fn main() { + eprintln!("ERROR: This binary requires the phase3-e2e feature."); + eprintln!("Run with: cargo run -p e2e-tests --bin print_registration_info --features phase3-e2e"); + std::process::exit(1); +} + +#[cfg(feature = "phase3-e2e")] +fn main() { + println!("=== Yield Agent Registration Info ===\n"); + + // Agent Code Hash + let agent_code_hash = example_yield_agent::AGENT_CODE_HASH; + println!("AGENT_CODE_HASH (bytes32):"); + println!(" 0x{}", hex::encode(&agent_code_hash)); + println!(); + + // Note about IMAGE_ID + println!("IMAGE_ID:"); + println!(" The IMAGE_ID must be obtained from the methods crate after building"); + println!(" the kernel-guest with --features example-yield-agent."); + println!(); + println!(" To get it, run the existing e2e tests with --nocapture:"); + println!(" cargo test -p e2e-tests --features risc0-e2e test_e2e_success_with_echo -- --nocapture"); + println!(); + println!(" Or add this to your test:"); + println!(" ```rust"); + println!(" use methods::ZKVM_GUEST_ID;"); + println!(" let image_id: Vec = ZKVM_GUEST_ID.iter().flat_map(|x| x.to_le_bytes()).collect();"); + println!(" println!(\"imageId: 0x{{}}\", hex::encode(&image_id));"); + println!(" ```"); + println!(); + + // Registration commands + println!("=== On-Chain Registration Commands ===\n"); + println!("1. First, deploy KernelExecutionVerifier with RISC Zero verifier address"); + println!(); + println!("2. Register the agent (replace IMAGE_ID with actual value):"); + println!(" cast send $VERIFIER_ADDRESS \\"); + println!(" \"registerAgent(bytes32,bytes32)\" \\"); + println!(" $AGENT_ID \\"); + println!(" $IMAGE_ID \\"); + println!(" --private-key $PRIVATE_KEY --rpc-url $RPC_URL"); + println!(); + println!("3. Deploy KernelVault with:"); + println!(" - _asset: Your ERC20 token address (or use WETH)"); + println!(" - _verifier: KernelExecutionVerifier address"); + println!(" - _agentId: The AGENT_ID you chose (any bytes32, used to identify this vault's agent)"); + println!(); + + // Example values + println!("=== Example Values ===\n"); + println!("For testing, you can use any bytes32 as AGENT_ID:"); + println!(" AGENT_ID=0x4242424242424242424242424242424242424242424242424242424242424242"); + println!(); + println!("The AGENT_ID in KernelInputV1 must match the vault's agentId."); + println!("The agent_code_hash in KernelInputV1 must match: 0x{}", hex::encode(&agent_code_hash)); +} diff --git a/crates/e2e-tests/src/lib.rs b/crates/testing/e2e-tests/src/lib.rs similarity index 68% rename from crates/e2e-tests/src/lib.rs rename to crates/testing/e2e-tests/src/lib.rs index 0e3ffea..4325cc4 100644 --- a/crates/e2e-tests/src/lib.rs +++ b/crates/testing/e2e-tests/src/lib.rs @@ -5,9 +5,9 @@ //! //! # Test Coverage //! -//! 1. **Success Path**: Valid input with echo action produces valid proof +//! 1. **Success Path**: Valid input produces valid proof with yield agent //! 2. **Hash Mismatch**: Wrong agent_code_hash fails during guest execution -//! 3. **Empty Output**: No-echo input produces empty output with correct commitment +//! 3. **Empty Output**: Invalid input (wrong size) produces empty output //! //! # Running Tests //! @@ -28,21 +28,31 @@ #![cfg_attr(not(feature = "risc0-e2e"), allow(dead_code))] +// Phase 3 E2E Yield Tests +#[cfg(feature = "phase3-e2e")] +pub mod phase3_yield; + use kernel_core::{ compute_action_commitment, compute_input_commitment, AgentOutput, CanonicalDecode, CanonicalEncode, ExecutionStatus, KernelInputV1, KernelJournalV1, KERNEL_VERSION, PROTOCOL_VERSION, }; -/// Helper to construct a valid KernelInputV1 with the correct agent_code_hash. +/// Helper to construct a valid KernelInputV1 with the correct agent_code_hash for yield agent. /// -/// Uses `example_agent::AGENT_CODE_HASH` to ensure hash verification passes. -pub fn make_valid_input(opaque_agent_inputs: Vec) -> KernelInputV1 { +/// Uses `example_yield_agent::AGENT_CODE_HASH` to ensure hash verification passes. +/// Creates a 48-byte opaque input with: vault (20 bytes) + yield source (20 bytes) + amount (8 bytes). +pub fn make_valid_input(vault: [u8; 20], yield_source: [u8; 20], amount: u64) -> KernelInputV1 { + let mut opaque_agent_inputs = Vec::with_capacity(48); + opaque_agent_inputs.extend_from_slice(&vault); + opaque_agent_inputs.extend_from_slice(&yield_source); + opaque_agent_inputs.extend_from_slice(&amount.to_le_bytes()); + KernelInputV1 { protocol_version: PROTOCOL_VERSION, kernel_version: KERNEL_VERSION, agent_id: [0x42; 32], - agent_code_hash: example_agent::AGENT_CODE_HASH, + agent_code_hash: example_yield_agent::AGENT_CODE_HASH, constraint_set_hash: [0xbb; 32], input_root: [0xcc; 32], execution_nonce: 1, @@ -53,7 +63,12 @@ pub fn make_valid_input(opaque_agent_inputs: Vec) -> KernelInputV1 { /// Helper to construct a KernelInputV1 with a WRONG agent_code_hash. /// /// Used to test that hash mismatches cause execution failures. -pub fn make_input_with_wrong_hash(opaque_agent_inputs: Vec) -> KernelInputV1 { +pub fn make_input_with_wrong_hash(vault: [u8; 20], yield_source: [u8; 20], amount: u64) -> KernelInputV1 { + let mut opaque_agent_inputs = Vec::with_capacity(48); + opaque_agent_inputs.extend_from_slice(&vault); + opaque_agent_inputs.extend_from_slice(&yield_source); + opaque_agent_inputs.extend_from_slice(&amount.to_le_bytes()); + KernelInputV1 { protocol_version: PROTOCOL_VERSION, kernel_version: KERNEL_VERSION, @@ -66,25 +81,44 @@ pub fn make_input_with_wrong_hash(opaque_agent_inputs: Vec) -> KernelInputV1 } } -/// Compute the expected action commitment for an echo output. +/// Helper to construct a KernelInputV1 with invalid input size (wrong for yield agent). +/// +/// Used to test that invalid inputs produce empty output. +pub fn make_input_with_invalid_size(opaque_agent_inputs: Vec) -> KernelInputV1 { + KernelInputV1 { + protocol_version: PROTOCOL_VERSION, + kernel_version: KERNEL_VERSION, + agent_id: [0x42; 32], + agent_code_hash: example_yield_agent::AGENT_CODE_HASH, + constraint_set_hash: [0xbb; 32], + input_root: [0xcc; 32], + execution_nonce: 1, + opaque_agent_inputs, + } +} + +/// Compute the expected action commitment for yield agent output. /// -/// When the example-agent echoes, it produces: -/// - action_type: 0x00000001 (ECHO) -/// - target: agent_id -/// - payload: opaque_inputs (truncated to MAX_ACTION_PAYLOAD_BYTES) -pub fn compute_echo_commitment(agent_id: [u8; 32], payload: &[u8]) -> [u8; 32] { +/// When the yield agent receives valid 48-byte input, it produces: +/// - Two CALL actions (deposit and withdraw) +pub fn compute_yield_commitment(vault: [u8; 20], yield_source: [u8; 20], amount: u64) -> [u8; 32] { use kernel_core::ActionV1; - use kernel_sdk::types::MAX_ACTION_PAYLOAD_BYTES; + use kernel_sdk::prelude::{call_action, address_to_bytes32, ACTION_TYPE_CALL}; - let payload_len = payload.len().min(MAX_ACTION_PAYLOAD_BYTES); - let action = ActionV1 { - action_type: 0x00000001, // ACTION_TYPE_ECHO - target: agent_id, - payload: payload[..payload_len].to_vec(), - }; + let target = address_to_bytes32(&yield_source); + + // Build deposit action (same logic as yield agent) + let deposit_action = call_action(target, amount as u128, &[]); + + // Build withdraw action (same logic as yield agent) + let withdraw_selector: [u8; 4] = [0x51, 0xcf, 0xf8, 0xd9]; + let mut withdraw_calldata = Vec::with_capacity(36); + withdraw_calldata.extend_from_slice(&withdraw_selector); + withdraw_calldata.extend_from_slice(&address_to_bytes32(&vault)); + let withdraw_action = call_action(target, 0, &withdraw_calldata); let output = AgentOutput { - actions: vec![action], + actions: vec![deposit_action, withdraw_action], }; let output_bytes = output.encode().expect("encode should succeed"); @@ -99,28 +133,30 @@ pub fn compute_echo_commitment(agent_id: [u8; 32], payload: &[u8]) -> [u8; 32] { mod zkvm_tests { use super::*; use constraints::EMPTY_OUTPUT_COMMITMENT; - use methods::{ZKVM_GUEST_ELF, ZKVM_GUEST_ID}; + use risc0_methods::{ZKVM_GUEST_ELF, ZKVM_GUEST_ID}; use risc0_zkvm::{default_prover, ExecutorEnv, ProverOpts}; - /// Test 1: Successful execution with echo action produces valid proof. + /// Test 1: Successful execution with yield agent produces valid proof. /// /// This test verifies the complete happy path: - /// 1. Construct valid input with echo trigger (first byte = 1) + /// 1. Construct valid 48-byte input for yield agent /// 2. Run zkVM prover to execute kernel-guest /// 3. Verify receipt against IMAGE_ID /// 4. Decode journal and verify: /// - execution_status == Success /// - input_commitment matches SHA256(input_bytes) - /// - action_commitment matches expected echo output + /// - action_commitment matches expected yield output #[test] - fn test_e2e_success_with_echo() { - // Construct input that triggers echo (first byte = 1) - let opaque_inputs = vec![1, 2, 3, 4, 5]; - let input = make_valid_input(opaque_inputs.clone()); + fn test_e2e_success_with_yield_agent() { + // Construct valid input for yield agent (48 bytes) + let vault = [0x11u8; 20]; + let yield_source = [0x22u8; 20]; + let amount: u64 = 1_000_000_000_000_000_000; // 1 ETH + + let input = make_valid_input(vault, yield_source, amount); let input_bytes = input.encode().expect("encode should succeed"); // Build executor environment with input - // Use .write() to serialize the Vec so env::read() can deserialize it let env = ExecutorEnv::builder() .write(&input_bytes) .expect("failed to write input") @@ -162,7 +198,7 @@ mod zkvm_tests { assert_eq!(journal.protocol_version, PROTOCOL_VERSION); assert_eq!(journal.kernel_version, KERNEL_VERSION); assert_eq!(journal.agent_id, [0x42; 32]); - assert_eq!(journal.agent_code_hash, example_agent::AGENT_CODE_HASH); + assert_eq!(journal.agent_code_hash, example_yield_agent::AGENT_CODE_HASH); assert_eq!(journal.constraint_set_hash, [0xbb; 32]); assert_eq!(journal.input_root, [0xcc; 32]); assert_eq!(journal.execution_nonce, 1); @@ -174,15 +210,14 @@ mod zkvm_tests { "Input commitment mismatch" ); - // Verify action commitment (echo action) - let expected_action_commitment = compute_echo_commitment([0x42; 32], &opaque_inputs); + // Verify action commitment (yield agent produces 2 CALL actions) + let expected_action_commitment = compute_yield_commitment(vault, yield_source, amount); assert_eq!( journal.action_commitment, expected_action_commitment, "Action commitment mismatch" ); - + // Extract seal for on-chain verification - // The seal is inside the Groth16Receipt if let risc0_zkvm::InnerReceipt::Groth16(groth16_receipt) = &receipt.inner { // Convert image_id [u32; 8] to bytes32 (little-endian) let image_id_bytes: Vec = ZKVM_GUEST_ID @@ -194,7 +229,6 @@ mod zkvm_tests { let agent_id_bytes: [u8; 32] = [0x42; 32]; // The on-chain verifier expects: [4-byte selector][256-byte seal] - // The selector is the first 4 bytes of verifier_parameters digest let selector = &groth16_receipt.verifier_parameters.as_bytes()[..4]; let mut encoded_seal = Vec::with_capacity(4 + groth16_receipt.seal.len()); encoded_seal.extend_from_slice(selector); @@ -222,7 +256,11 @@ mod zkvm_tests { #[test] fn test_e2e_agent_code_hash_mismatch() { // Construct input with WRONG agent_code_hash - let input = make_input_with_wrong_hash(vec![1, 2, 3]); + let vault = [0x11u8; 20]; + let yield_source = [0x22u8; 20]; + let amount: u64 = 1_000_000_000_000_000_000; + + let input = make_input_with_wrong_hash(vault, yield_source, amount); let input_bytes = input.encode().expect("encode should succeed"); // Build executor environment @@ -249,7 +287,6 @@ mod zkvm_tests { println!("Got expected error: {}", err_string); // The error should indicate guest execution failed - // (exact error message depends on risc0-zkvm version) assert!( err_string.contains("panic") || err_string.contains("failed") @@ -260,17 +297,17 @@ mod zkvm_tests { println!("Hash mismatch correctly caused execution failure!"); } - /// Test 3: Empty output when no-echo trigger produces correct commitment. + /// Test 3: Invalid input size produces empty output with correct commitment. /// - /// When opaque_inputs[0] != 1, the example-agent produces no actions. + /// When opaque_inputs is not exactly 48 bytes, the yield agent produces no actions. /// This should: /// - Still succeed (empty output is valid) /// - Have action_commitment == EMPTY_OUTPUT_COMMITMENT #[test] - fn test_e2e_empty_output() { - // Construct input that does NOT trigger echo (first byte = 0) - let opaque_inputs = vec![0, 2, 3, 4, 5]; - let input = make_valid_input(opaque_inputs); + fn test_e2e_empty_output_invalid_input_size() { + // Construct input with wrong size (not 48 bytes) + let opaque_inputs = vec![0, 2, 3, 4, 5]; // Only 5 bytes - invalid for yield agent + let input = make_input_with_invalid_size(opaque_inputs); let input_bytes = input.encode().expect("encode should succeed"); // Build executor environment @@ -295,7 +332,7 @@ mod zkvm_tests { .verify(ZKVM_GUEST_ID) .expect("receipt verification failed"); - // Extract the journal bytes (raw bytes committed via env::commit_slice) + // Extract the journal bytes let journal_bytes = receipt.journal.bytes.clone(); let journal = KernelJournalV1::decode(&journal_bytes).expect("KernelJournalV1 decode failed"); @@ -310,7 +347,7 @@ mod zkvm_tests { // Verify action commitment is the empty output commitment assert_eq!( journal.action_commitment, EMPTY_OUTPUT_COMMITMENT, - "Expected EMPTY_OUTPUT_COMMITMENT for no-echo case" + "Expected EMPTY_OUTPUT_COMMITMENT for invalid input size" ); // Also verify against manually computed empty commitment @@ -331,8 +368,12 @@ mod zkvm_tests { /// demonstrating deterministic execution. #[test] fn test_e2e_determinism() { - // Construct input - let input = make_valid_input(vec![1, 0xde, 0xad, 0xbe, 0xef]); + // Construct valid input for yield agent + let vault = [0xde; 20]; + let yield_source = [0xad; 20]; + let amount: u64 = 0xdeadbeef; + + let input = make_valid_input(vault, yield_source, amount); let input_bytes = input.encode().expect("encode should succeed"); // Run prover twice @@ -376,31 +417,46 @@ mod unit_tests { #[test] fn test_make_valid_input_uses_correct_hash() { - let input = make_valid_input(vec![1, 2, 3]); - assert_eq!(input.agent_code_hash, example_agent::AGENT_CODE_HASH); + let vault = [0x11; 20]; + let yield_source = [0x22; 20]; + let input = make_valid_input(vault, yield_source, 1000); + assert_eq!(input.agent_code_hash, example_yield_agent::AGENT_CODE_HASH); } #[test] fn test_make_input_with_wrong_hash_is_wrong() { - let input = make_input_with_wrong_hash(vec![1, 2, 3]); - assert_ne!(input.agent_code_hash, example_agent::AGENT_CODE_HASH); + let vault = [0x11; 20]; + let yield_source = [0x22; 20]; + let input = make_input_with_wrong_hash(vault, yield_source, 1000); + assert_ne!(input.agent_code_hash, example_yield_agent::AGENT_CODE_HASH); assert_eq!(input.agent_code_hash, [0x00; 32]); } #[test] - fn test_compute_echo_commitment_is_deterministic() { - let agent_id = [0x42; 32]; - let payload = vec![1, 2, 3, 4, 5]; + fn test_make_valid_input_creates_48_byte_opaque_input() { + let vault = [0x11; 20]; + let yield_source = [0x22; 20]; + let input = make_valid_input(vault, yield_source, 1000); + assert_eq!(input.opaque_agent_inputs.len(), 48); + } + + #[test] + fn test_compute_yield_commitment_is_deterministic() { + let vault = [0x11; 20]; + let yield_source = [0x22; 20]; + let amount = 1_000_000_000u64; - let commitment1 = compute_echo_commitment(agent_id, &payload); - let commitment2 = compute_echo_commitment(agent_id, &payload); + let commitment1 = compute_yield_commitment(vault, yield_source, amount); + let commitment2 = compute_yield_commitment(vault, yield_source, amount); assert_eq!(commitment1, commitment2); } #[test] fn test_input_encoding_roundtrip() { - let input = make_valid_input(vec![1, 2, 3, 4, 5]); + let vault = [0x11; 20]; + let yield_source = [0x22; 20]; + let input = make_valid_input(vault, yield_source, 1_000_000); let encoded = input.encode().expect("encode should succeed"); let decoded = KernelInputV1::decode(&encoded).expect("decode should succeed"); diff --git a/crates/testing/e2e-tests/src/phase3_yield.rs b/crates/testing/e2e-tests/src/phase3_yield.rs new file mode 100644 index 0000000..6728767 --- /dev/null +++ b/crates/testing/e2e-tests/src/phase3_yield.rs @@ -0,0 +1,609 @@ +//! Phase 3 E2E Yield Test +//! +//! This module tests the complete execution flow: +//! Vault -> ZK Agent -> ETH leaves vault -> ETH returns with +10% -> Vault PPS increases +//! +//! # Prerequisites +//! +//! 1. KernelVault deployed with correct agentId and imageId registered +//! 2. MockYieldSource deployed and funded with yield reserves +//! 3. Vault has ETH to execute the deposit +//! 4. RISC Zero toolchain installed with Groth16 support +//! +//! # Environment Variables +//! +//! - `VAULT_ADDRESS`: Deployed KernelVault address +//! - `MOCK_YIELD_ADDRESS`: Deployed MockYieldSource address +//! - `RPC_URL`: Ethereum RPC endpoint +//! - `PRIVATE_KEY`: Private key for transaction signing +//! - `EXECUTION_NONCE`: Nonce for this execution (must match vault state) +//! - `TRANSFER_AMOUNT`: Amount of ETH to transfer (in wei) +//! +//! # Running +//! +//! ```bash +//! VAULT_ADDRESS=0x... \ +//! MOCK_YIELD_ADDRESS=0x... \ +//! RPC_URL=http://localhost:8545 \ +//! PRIVATE_KEY=0x... \ +//! EXECUTION_NONCE=1 \ +//! TRANSFER_AMOUNT=1000000000000000000 \ +//! cargo test --release -p e2e-tests --features phase3-e2e phase3_yield -- --nocapture +//! ``` + +#![cfg(feature = "phase3-e2e")] + +use kernel_core::{CanonicalEncode, KernelInputV1, KERNEL_VERSION, PROTOCOL_VERSION}; + +/// Build the 48-byte agent input for the yield agent. +/// +/// Format: +/// - [0:20] vault_address (20 bytes) +/// - [20:40] mock_yield_address (20 bytes) +/// - [40:48] transfer_amount (u64 LE) +pub fn build_yield_agent_input( + vault_address: &[u8; 20], + mock_yield_address: &[u8; 20], + transfer_amount: u64, +) -> Vec { + let mut input = Vec::with_capacity(48); + input.extend_from_slice(vault_address); + input.extend_from_slice(mock_yield_address); + input.extend_from_slice(&transfer_amount.to_le_bytes()); + input +} + +/// Parse a hex address string (with or without 0x prefix) to [u8; 20]. +pub fn parse_address(hex_str: &str) -> Result<[u8; 20], String> { + let hex_str = hex_str.trim_start_matches("0x"); + if hex_str.len() != 40 { + return Err(format!("Invalid address length: {}", hex_str.len())); + } + let bytes = hex::decode(hex_str).map_err(|e| format!("Invalid hex: {}", e))?; + let arr: [u8; 20] = bytes.try_into().map_err(|_| "Invalid address length")?; + Ok(arr) +} + +/// Build a KernelInputV1 for the yield agent. +pub fn build_kernel_input( + agent_id: [u8; 32], + execution_nonce: u64, + vault_address: &[u8; 20], + mock_yield_address: &[u8; 20], + transfer_amount: u64, +) -> KernelInputV1 { + KernelInputV1 { + protocol_version: PROTOCOL_VERSION, + kernel_version: KERNEL_VERSION, + agent_id, + agent_code_hash: example_yield_agent::AGENT_CODE_HASH, + constraint_set_hash: [0xbb; 32], // Default constraint set + input_root: [0; 32], + execution_nonce, + opaque_agent_inputs: build_yield_agent_input(vault_address, mock_yield_address, transfer_amount), + } +} + +/// Compute the agent output bytes for verification. +/// +/// This reconstructs what the agent would output given the inputs, +/// which is needed for the vault.execute() call. +pub fn compute_agent_output_bytes( + vault_address: &[u8; 20], + mock_yield_address: &[u8; 20], + transfer_amount: u64, +) -> Vec { + use kernel_sdk::prelude::{address_to_bytes32, call_action, AgentOutput}; + + // Reconstruct the same actions the agent produces + let target = address_to_bytes32(mock_yield_address); + + // Action 1: Deposit + let deposit_action = call_action(target, transfer_amount as u128, &[]); + + // Action 2: Withdraw + let withdraw_selector: [u8; 4] = [0x51, 0xcf, 0xf8, 0xd9]; + let mut withdraw_calldata = Vec::with_capacity(36); + withdraw_calldata.extend_from_slice(&withdraw_selector); + withdraw_calldata.extend_from_slice(&address_to_bytes32(vault_address)); + let withdraw_action = call_action(target, 0, &withdraw_calldata); + + // Actions are now preserved in order (deposit first, then withdraw) + let output = AgentOutput { + actions: vec![deposit_action, withdraw_action], + }; + + output.encode().expect("encode should succeed") +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_build_yield_agent_input() { + let vault = [0x11u8; 20]; + let yield_source = [0x22u8; 20]; + let amount: u64 = 1_000_000_000_000_000_000; + + let input = build_yield_agent_input(&vault, &yield_source, amount); + + assert_eq!(input.len(), 48); + assert_eq!(&input[0..20], &vault); + assert_eq!(&input[20..40], &yield_source); + assert_eq!( + u64::from_le_bytes(input[40..48].try_into().unwrap()), + amount + ); + } + + #[test] + fn test_parse_address() { + let addr = parse_address("0x1234567890123456789012345678901234567890").unwrap(); + assert_eq!( + addr, + [ + 0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x34, 0x56, 0x78, + 0x90, 0x12, 0x34, 0x56, 0x78, 0x90 + ] + ); + + // Without 0x prefix + let addr2 = parse_address("1234567890123456789012345678901234567890").unwrap(); + assert_eq!(addr, addr2); + } + + #[test] + fn test_build_kernel_input() { + let agent_id = [0x42u8; 32]; + let vault = [0x11u8; 20]; + let yield_source = [0x22u8; 20]; + let amount: u64 = 1_000_000_000_000_000_000; + + let input = build_kernel_input(agent_id, 1, &vault, &yield_source, amount); + + assert_eq!(input.protocol_version, PROTOCOL_VERSION); + assert_eq!(input.kernel_version, KERNEL_VERSION); + assert_eq!(input.agent_id, agent_id); + assert_eq!(input.agent_code_hash, example_yield_agent::AGENT_CODE_HASH); + assert_eq!(input.execution_nonce, 1); + assert_eq!(input.opaque_agent_inputs.len(), 48); + } + + #[test] + fn test_compute_agent_output_bytes() { + let vault = [0x11u8; 20]; + let yield_source = [0x22u8; 20]; + let amount: u64 = 1_000_000_000_000_000_000; + + let output_bytes = compute_agent_output_bytes(&vault, &yield_source, amount); + + // Output should have 2 actions + // This is a basic sanity check - detailed validation is done elsewhere + assert!(!output_bytes.is_empty()); + } +} + +// ============================================================================ +// Registration Info (prints IMAGE_ID and AGENT_CODE_HASH) +// ============================================================================ + +#[cfg(all(test, feature = "phase3-e2e"))] +mod registration_info { + use risc0_methods::ZKVM_GUEST_ID; + + /// Print the registration values needed for on-chain deployment. + /// + /// Run with: + /// ```bash + /// cargo test -p e2e-tests --features phase3-e2e print_yield_agent_registration_info -- --nocapture + /// ``` + #[test] + fn print_yield_agent_registration_info() { + println!("\n"); + println!("╔══════════════════════════════════════════════════════════════════╗"); + println!("║ YIELD AGENT ON-CHAIN REGISTRATION VALUES ║"); + println!("╚══════════════════════════════════════════════════════════════════╝"); + println!(); + + // IMAGE_ID (from methods crate - depends on zkvm-guest build) + let image_id_bytes: Vec = ZKVM_GUEST_ID + .iter() + .flat_map(|x| x.to_le_bytes()) + .collect(); + println!("IMAGE_ID (for KernelExecutionVerifier.registerAgent):"); + println!(" 0x{}", hex::encode(&image_id_bytes)); + println!(); + + // AGENT_CODE_HASH + println!("AGENT_CODE_HASH (for KernelInputV1.agent_code_hash):"); + println!(" 0x{}", hex::encode(&example_yield_agent::AGENT_CODE_HASH)); + println!(); + + // AGENT_ID explanation + println!("AGENT_ID (for KernelVault constructor & KernelInputV1.agent_id):"); + println!(" This is any bytes32 YOU choose to identify this vault/agent pair."); + println!(" Example: 0x0000000000000000000000000000000000000000000000000000000000000001"); + println!(); + + println!("══════════════════════════════════════════════════════════════════"); + println!("Registration commands (using Foundry cast):"); + println!("══════════════════════════════════════════════════════════════════"); + println!(); + println!("export IMAGE_ID=0x{}", hex::encode(&image_id_bytes)); + println!("export AGENT_ID=0x0000000000000000000000000000000000000000000000000000000000000001"); + println!(); + println!("# Register agent with verifier:"); + println!("cast send $VERIFIER_ADDRESS \"registerAgent(bytes32,bytes32)\" $AGENT_ID $IMAGE_ID --private-key $PRIVATE_KEY --rpc-url $RPC_URL"); + println!(); + } +} + +// ============================================================================ +// zkVM Proof Generation Tests +// ============================================================================ + +#[cfg(all(test, feature = "phase3-e2e"))] +mod zkvm_tests { + use super::*; + use kernel_core::{ + compute_action_commitment, compute_input_commitment, CanonicalDecode, ExecutionStatus, + KernelJournalV1, + }; + use risc0_methods::{ZKVM_GUEST_ELF, ZKVM_GUEST_ID}; + use risc0_zkvm::{default_prover, ExecutorEnv, ProverOpts}; + + /// Test that the yield agent produces a valid proof with correct journal. + /// + /// This test runs the yield agent through the zkVM prover and verifies + /// the resulting proof and journal contents. + /// + /// NOTE: This test requires the kernel-guest to be built with the + /// `example-yield-agent` feature. The methods crate must be rebuilt + /// with this feature enabled. + #[test] + #[ignore = "requires kernel-guest built with example-yield-agent feature"] + fn test_yield_agent_proof_generation() { + let agent_id = [0x42u8; 32]; + let vault = [0x11u8; 20]; + let yield_source = [0x22u8; 20]; + let amount: u64 = 1_000_000_000_000_000_000; // 1 ETH + + // Build kernel input + let input = build_kernel_input(agent_id, 1, &vault, &yield_source, amount); + let input_bytes = input.encode().expect("encode should succeed"); + + // Build executor environment + let env = ExecutorEnv::builder() + .write(&input_bytes) + .expect("failed to write input") + .build() + .expect("failed to build executor env"); + + // Run the prover + println!("Starting zkVM proof generation for yield agent..."); + let prover = default_prover(); + let prove_info = prover + .prove_with_opts(env, ZKVM_GUEST_ELF, &ProverOpts::groth16()) + .expect("proof generation failed"); + + println!("Proof generated successfully!"); + + // Verify receipt + let receipt = prove_info.receipt; + receipt + .verify(ZKVM_GUEST_ID) + .expect("receipt verification failed"); + + println!("Receipt verified against IMAGE_ID"); + + // Decode journal + let journal_bytes = receipt.journal.bytes.clone(); + let journal = + KernelJournalV1::decode(&journal_bytes).expect("KernelJournalV1 decode failed"); + + // Verify execution succeeded + assert_eq!( + journal.execution_status, + ExecutionStatus::Success, + "Expected Success status" + ); + + // Verify identity fields + assert_eq!(journal.protocol_version, PROTOCOL_VERSION); + assert_eq!(journal.kernel_version, KERNEL_VERSION); + assert_eq!(journal.agent_id, agent_id); + assert_eq!(journal.agent_code_hash, example_yield_agent::AGENT_CODE_HASH); + assert_eq!(journal.execution_nonce, 1); + + // Verify input commitment + let expected_input_commitment = compute_input_commitment(&input_bytes); + assert_eq!( + journal.input_commitment, expected_input_commitment, + "Input commitment mismatch" + ); + + // Verify action commitment + let expected_output_bytes = compute_agent_output_bytes(&vault, &yield_source, amount); + let expected_action_commitment = compute_action_commitment(&expected_output_bytes); + assert_eq!( + journal.action_commitment, expected_action_commitment, + "Action commitment mismatch" + ); + + // Extract seal for on-chain verification + if let risc0_zkvm::InnerReceipt::Groth16(groth16_receipt) = &receipt.inner { + let selector = &groth16_receipt.verifier_parameters.as_bytes()[..4]; + let mut encoded_seal = Vec::with_capacity(4 + groth16_receipt.seal.len()); + encoded_seal.extend_from_slice(selector); + encoded_seal.extend_from_slice(&groth16_receipt.seal); + + let image_id_bytes: Vec = ZKVM_GUEST_ID + .iter() + .flat_map(|x| x.to_le_bytes()) + .collect(); + + println!("\n=== On-chain verification data (yield agent) ==="); + println!("seal (with selector, hex): 0x{}", hex::encode(&encoded_seal)); + println!("seal length: {} bytes", encoded_seal.len()); + println!("journal (hex): 0x{}", hex::encode(&journal_bytes)); + println!("journal length: {} bytes", journal_bytes.len()); + println!("image_id (bytes32): 0x{}", hex::encode(&image_id_bytes)); + println!("agent_id (bytes32): 0x{}", hex::encode(&agent_id)); + println!( + "agent_code_hash (bytes32): 0x{}", + hex::encode(&example_yield_agent::AGENT_CODE_HASH) + ); + println!( + "agent_output (hex): 0x{}", + hex::encode(&expected_output_bytes) + ); + } + + println!("\nYield agent proof generation test passed!"); + } +} + +// ============================================================================ +// On-Chain E2E Tests +// ============================================================================ + +#[cfg(all(test, feature = "phase3-e2e"))] +mod onchain_tests { + use super::*; + use alloy::{ + network::EthereumWallet, + primitives::{Address, Bytes, FixedBytes, U256}, + providers::{Provider, ProviderBuilder}, + signers::local::PrivateKeySigner, + sol, + }; + use std::env; + use std::str::FromStr; + + // Define the KernelVault interface + sol! { + #[sol(rpc)] + interface IKernelVault { + function execute(bytes calldata journal, bytes calldata seal, bytes calldata agentOutputBytes) external; + function lastExecutionNonce() external view returns (uint64); + function lastExecutionTimestamp() external view returns (uint64); + function agentId() external view returns (bytes32); + } + + #[sol(rpc)] + interface IMockYieldSource { + function deposits(address depositor) external view returns (uint256); + } + } + + /// Load test configuration from environment variables. + fn load_config() -> Result { + // Try to load .env file (optional) + let _ = dotenvy::dotenv(); + + Ok(TestConfig { + vault_address: env::var("VAULT_ADDRESS") + .map_err(|_| "VAULT_ADDRESS not set")? + .parse() + .map_err(|e| format!("Invalid VAULT_ADDRESS: {}", e))?, + mock_yield_address: env::var("MOCK_YIELD_ADDRESS") + .map_err(|_| "MOCK_YIELD_ADDRESS not set")? + .parse() + .map_err(|e| format!("Invalid MOCK_YIELD_ADDRESS: {}", e))?, + rpc_url: env::var("RPC_URL").map_err(|_| "RPC_URL not set")?, + private_key: env::var("PRIVATE_KEY").map_err(|_| "PRIVATE_KEY not set")?, + execution_nonce: env::var("EXECUTION_NONCE") + .map_err(|_| "EXECUTION_NONCE not set")? + .parse() + .map_err(|e| format!("Invalid EXECUTION_NONCE: {}", e))?, + transfer_amount: env::var("TRANSFER_AMOUNT") + .map_err(|_| "TRANSFER_AMOUNT not set")? + .parse() + .map_err(|e| format!("Invalid TRANSFER_AMOUNT: {}", e))?, + }) + } + + struct TestConfig { + vault_address: Address, + mock_yield_address: Address, + rpc_url: String, + private_key: String, + execution_nonce: u64, + transfer_amount: u64, + } + + /// Full E2E test that: + /// 1. Generates a zkVM proof + /// 2. Submits it to the deployed KernelVault + /// 3. Verifies the execution results + /// + /// This test requires: + /// - Deployed contracts (KernelVault, MockYieldSource) + /// - Funded vault with ETH + /// - Correct environment variables + #[tokio::test] + #[ignore = "requires deployed contracts and environment setup"] + async fn test_full_e2e_yield_execution() { + use kernel_core::{CanonicalDecode, ExecutionStatus, KernelJournalV1}; + use risc0_methods::{ZKVM_GUEST_ELF, ZKVM_GUEST_ID}; + use risc0_zkvm::{default_prover, ExecutorEnv, ProverOpts}; + + // Load configuration + let config = load_config().expect("Failed to load test config"); + + // Convert addresses to [u8; 20] + let vault_bytes: [u8; 20] = config.vault_address.into_array(); + let yield_source_bytes: [u8; 20] = config.mock_yield_address.into_array(); + + // Setup provider and wallet with recommended fillers for gas estimation + let signer = PrivateKeySigner::from_str(&config.private_key) + .expect("Invalid private key"); + let wallet = EthereumWallet::from(signer); + let provider = ProviderBuilder::new() + .with_recommended_fillers() + .wallet(wallet) + .on_http(config.rpc_url.parse().expect("Invalid RPC URL")); + + // Create contract instances + let vault = IKernelVault::new(config.vault_address, &provider); + let yield_source = IMockYieldSource::new(config.mock_yield_address, &provider); + + // Get initial state + let initial_nonce = vault.lastExecutionNonce().call().await.expect("Failed to get nonce")._0; + let initial_vault_balance = provider + .get_balance(config.vault_address) + .await + .expect("Failed to get balance"); + let agent_id_bytes32: FixedBytes<32> = vault.agentId().call().await.expect("Failed to get agentId")._0; + let agent_id: [u8; 32] = agent_id_bytes32.into(); + + println!("=== Initial State ==="); + println!("Vault address: {}", config.vault_address); + println!("MockYieldSource address: {}", config.mock_yield_address); + println!("Initial nonce: {}", initial_nonce); + println!("Initial vault balance: {} wei", initial_vault_balance); + println!("Agent ID: 0x{}", hex::encode(&agent_id)); + println!("Transfer amount: {} wei", config.transfer_amount); + + // Build kernel input + let kernel_input = build_kernel_input( + agent_id, + config.execution_nonce, + &vault_bytes, + &yield_source_bytes, + config.transfer_amount, + ); + let input_bytes = kernel_input.encode().expect("encode should succeed"); + + // Generate zkVM proof + println!("\n=== Generating zkVM Proof ==="); + let env = ExecutorEnv::builder() + .write(&input_bytes) + .expect("failed to write input") + .build() + .expect("failed to build executor env"); + + let prover = default_prover(); + let prove_info = prover + .prove_with_opts(env, ZKVM_GUEST_ELF, &ProverOpts::groth16()) + .expect("proof generation failed"); + + let receipt = prove_info.receipt; + receipt + .verify(ZKVM_GUEST_ID) + .expect("receipt verification failed"); + + println!("Proof generated and verified!"); + + // Extract journal and seal + let journal_bytes = receipt.journal.bytes.clone(); + let journal = KernelJournalV1::decode(&journal_bytes).expect("decode failed"); + + assert_eq!(journal.execution_status, ExecutionStatus::Success); + + // Extract Groth16 seal with selector prefix + let encoded_seal = if let risc0_zkvm::InnerReceipt::Groth16(groth16_receipt) = &receipt.inner + { + let selector = &groth16_receipt.verifier_parameters.as_bytes()[..4]; + let mut seal = Vec::with_capacity(4 + groth16_receipt.seal.len()); + seal.extend_from_slice(selector); + seal.extend_from_slice(&groth16_receipt.seal); + seal + } else { + panic!("Expected Groth16 receipt"); + }; + + // Compute agent output bytes + let agent_output_bytes = compute_agent_output_bytes( + &vault_bytes, + &yield_source_bytes, + config.transfer_amount, + ); + + println!("\n=== Submitting Transaction ==="); + println!("Journal length: {} bytes", journal_bytes.len()); + println!("Seal length: {} bytes", encoded_seal.len()); + println!("Agent output length: {} bytes", agent_output_bytes.len()); + + // Submit execute transaction + let tx = vault.execute( + Bytes::from(journal_bytes.clone()), + Bytes::from(encoded_seal), + Bytes::from(agent_output_bytes), + ); + + let pending_tx = tx.send().await.expect("Failed to send transaction"); + println!("Transaction sent: {:?}", pending_tx.tx_hash()); + + let tx_receipt = pending_tx + .get_receipt() + .await + .expect("Failed to get receipt"); + + println!("Transaction confirmed in block: {:?}", tx_receipt.block_number); + println!("Gas used: {:?}", tx_receipt.gas_used); + + // Verify results + println!("\n=== Verifying Results ==="); + + let final_nonce = vault.lastExecutionNonce().call().await.expect("Failed to get nonce")._0; + let final_vault_balance = provider + .get_balance(config.vault_address) + .await + .expect("Failed to get balance"); + let yield_source_deposit = yield_source + .deposits(config.vault_address) + .call() + .await + .expect("Failed to get deposits")._0; + + println!("Final nonce: {}", final_nonce); + println!("Final vault balance: {} wei", final_vault_balance); + println!("MockYieldSource deposits[vault]: {} wei", yield_source_deposit); + + // Assertions + assert_eq!( + final_nonce, config.execution_nonce, + "Nonce should match execution nonce" + ); + + // After deposit and withdrawal, vault should have gained 10% yield + let expected_yield = config.transfer_amount / 10; + let expected_final_balance = initial_vault_balance + U256::from(expected_yield); + assert_eq!( + final_vault_balance, expected_final_balance, + "Vault should have gained 10% yield" + ); + + // MockYieldSource deposits should be cleared after withdrawal + assert_eq!( + yield_source_deposit, + U256::ZERO, + "MockYieldSource deposits should be cleared" + ); + + println!("\n=== E2E Test Passed! ==="); + println!("Yield earned: {} wei (10%)", expected_yield); + } +} diff --git a/crates/testing/kernel-host-tests/Cargo.toml b/crates/testing/kernel-host-tests/Cargo.toml new file mode 100644 index 0000000..5c36c3d --- /dev/null +++ b/crates/testing/kernel-host-tests/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "kernel-host-tests" +version = "0.1.0" +edition = "2021" + +[dependencies] +kernel-core = { path = "../../protocol/kernel-core", features = ["std"] } +kernel-guest = { path = "../../runtime/kernel-guest" } +kernel-sdk = { path = "../../sdk/kernel-sdk" } +constraints = { path = "../../protocol/constraints" } +# Wrapper crate that binds yield-agent to kernel-guest +kernel-guest-binding-yield = { path = "../../agents/wrappers/kernel-guest-binding-yield" } +example-yield-agent = { path = "../../agents/examples/example-yield-agent" } + +[dev-dependencies] +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" diff --git a/crates/host-tests/src/lib.rs b/crates/testing/kernel-host-tests/src/lib.rs similarity index 62% rename from crates/host-tests/src/lib.rs rename to crates/testing/kernel-host-tests/src/lib.rs index 6e1d9ac..dfd5540 100644 --- a/crates/host-tests/src/lib.rs +++ b/crates/testing/kernel-host-tests/src/lib.rs @@ -1,10 +1,5 @@ -// Force linking of example-agent to provide the agent_main symbol. -// This is required because kernel-guest declares an extern "Rust" fn agent_main -// which must be provided by a linked agent crate. -extern crate example_agent; - -// Re-export the agent code hash for tests to use. -pub use example_agent::AGENT_CODE_HASH; +// Re-export the agent code hash from the wrapper crate for tests to use. +pub use kernel_guest_binding_yield::AGENT_CODE_HASH; #[cfg(test)] mod tests { @@ -14,18 +9,25 @@ mod tests { get_u32_le, get_u64_le, get_bytes32, ensure_no_trailing_bytes, }; - use kernel_guest::kernel_main; + use kernel_guest_binding_yield::kernel_main; use constraints::EMPTY_OUTPUT_COMMITMENT; + use kernel_sdk::prelude::{call_action, address_to_bytes32}; - // Import the agent code hash from the linked example-agent crate. + // Import the agent code hash from the linked yield-agent crate. // This is the compile-time constant that the kernel verifies against. use crate::AGENT_CODE_HASH; - /// Helper to create a valid KernelInputV1 with the correct agent_code_hash. + /// Helper to create a valid KernelInputV1 with the correct agent_code_hash for yield agent. /// - /// This uses `AGENT_CODE_HASH` from the linked example-agent crate, + /// This uses `AGENT_CODE_HASH` from the linked yield-agent crate, /// ensuring the hash verification in kernel_main will pass. - fn make_input(agent_input: Vec) -> KernelInputV1 { + /// Creates 48-byte opaque input: vault (20 bytes) + yield_source (20 bytes) + amount (8 bytes). + fn make_input(vault: [u8; 20], yield_source: [u8; 20], amount: u64) -> KernelInputV1 { + let mut opaque_agent_inputs = Vec::with_capacity(48); + opaque_agent_inputs.extend_from_slice(&vault); + opaque_agent_inputs.extend_from_slice(&yield_source); + opaque_agent_inputs.extend_from_slice(&amount.to_le_bytes()); + KernelInputV1 { protocol_version: PROTOCOL_VERSION, kernel_version: KERNEL_VERSION, @@ -34,7 +36,21 @@ mod tests { constraint_set_hash: [0xbb; 32], input_root: [0xcc; 32], execution_nonce: 1, - opaque_agent_inputs: agent_input, + opaque_agent_inputs, + } + } + + /// Helper to create a KernelInputV1 with raw opaque input bytes. + fn make_input_raw(opaque_agent_inputs: Vec) -> KernelInputV1 { + KernelInputV1 { + protocol_version: PROTOCOL_VERSION, + kernel_version: KERNEL_VERSION, + agent_id: [0x42; 32], + agent_code_hash: AGENT_CODE_HASH, + constraint_set_hash: [0xbb; 32], + input_root: [0xcc; 32], + execution_nonce: 1, + opaque_agent_inputs, } } @@ -43,7 +59,12 @@ mod tests { /// This uses a dummy hash that will NOT match the linked agent, /// useful for testing hash mismatch scenarios. #[allow(dead_code)] - fn make_input_with_wrong_hash(agent_input: Vec) -> KernelInputV1 { + fn make_input_with_wrong_hash(vault: [u8; 20], yield_source: [u8; 20], amount: u64) -> KernelInputV1 { + let mut opaque_agent_inputs = Vec::with_capacity(48); + opaque_agent_inputs.extend_from_slice(&vault); + opaque_agent_inputs.extend_from_slice(&yield_source); + opaque_agent_inputs.extend_from_slice(&amount.to_le_bytes()); + KernelInputV1 { protocol_version: PROTOCOL_VERSION, kernel_version: KERNEL_VERSION, @@ -52,13 +73,13 @@ mod tests { constraint_set_hash: [0xbb; 32], input_root: [0xcc; 32], execution_nonce: 1, - opaque_agent_inputs: agent_input, + opaque_agent_inputs, } } #[test] fn test_kernel_input_encoding_round_trip() { - let original = make_input(vec![1, 2, 3, 4, 5]); + let original = make_input([0x11; 20], [0x22; 20], 1000); let encoded = original.encode().unwrap(); let decoded = KernelInputV1::decode(&encoded).unwrap(); @@ -218,7 +239,7 @@ mod tests { #[test] fn test_determinism() { - let input = make_input(vec![100, 200]); + let input = make_input([0x11; 20], [0x22; 20], 100); let input_bytes = input.encode().unwrap(); let result1 = kernel_main(&input_bytes).unwrap(); @@ -230,7 +251,7 @@ mod tests { #[test] fn test_invalid_protocol_version() { // Encode a valid input, then corrupt the protocol_version bytes - let input = make_input(vec![1, 2, 3]); + let input = make_input([0x11; 20], [0x22; 20], 1000); let mut input_bytes = input.encode().unwrap(); // Corrupt protocol_version to 999 (little-endian, first 4 bytes) @@ -243,7 +264,7 @@ mod tests { #[test] fn test_invalid_kernel_version() { // Encode a valid input, then corrupt the kernel_version bytes - let input = make_input(vec![1, 2, 3]); + let input = make_input([0x11; 20], [0x22; 20], 1000); let mut input_bytes = input.encode().unwrap(); // Corrupt kernel_version to 999 (little-endian, bytes 4-7) @@ -302,7 +323,7 @@ mod tests { #[test] fn test_input_too_large() { let large_input = vec![0u8; MAX_AGENT_INPUT_BYTES + 1]; - let input = make_input(large_input); + let input = make_input_raw(large_input); // Encode-side now catches oversized inputs let result = input.encode(); @@ -342,7 +363,7 @@ mod tests { #[test] fn test_constraints_enforcement() { - let input = make_input(vec![1, 2, 3]); + let input = make_input([0x11; 20], [0x22; 20], 1000); let input_bytes = input.encode().unwrap(); let result = kernel_main(&input_bytes); @@ -350,8 +371,9 @@ mod tests { } #[test] - fn test_empty_input() { - let input = make_input(vec![]); + fn test_empty_input_produces_empty_output() { + // Empty input (wrong size for yield agent) should produce empty output + let input = make_input_raw(vec![]); let input_bytes = input.encode().unwrap(); let result = kernel_main(&input_bytes); @@ -360,11 +382,14 @@ mod tests { let journal_bytes = result.unwrap(); let journal = KernelJournalV1::decode(&journal_bytes).unwrap(); assert_eq!(journal.execution_status, ExecutionStatus::Success); + // Yield agent returns empty output for invalid input size + assert_eq!(journal.action_commitment, EMPTY_OUTPUT_COMMITMENT); } #[test] fn test_max_size_input() { - let input = make_input(vec![0x42; MAX_AGENT_INPUT_BYTES]); + // Max size input - won't be 48 bytes so yield agent returns empty output + let input = make_input_raw(vec![0x42; MAX_AGENT_INPUT_BYTES]); let input_bytes = input.encode().unwrap(); let result = kernel_main(&input_bytes); @@ -381,7 +406,7 @@ mod tests { constraint_set_hash: [0x33; 32], input_root: [0x44; 32], execution_nonce: 9999, - opaque_agent_inputs: vec![1, 2, 3], + opaque_agent_inputs: vec![0u8; 48], // Valid size for yield agent (all zeros) }; let input_bytes = input.encode().unwrap(); @@ -474,13 +499,13 @@ mod tests { fn test_determinism_with_edge_cases() { let test_cases = vec![ vec![], // Empty - vec![0], // Single byte + vec![0; 48], // Valid size for yield agent vec![0xFF; 100], // Repeated bytes - (0..255).collect::>(), // Sequential bytes + (0..48).collect::>(), // Sequential bytes (valid size) ]; for test_input in test_cases { - let input = make_input(test_input); + let input = make_input_raw(test_input); let input_bytes = input.encode().unwrap(); // Run multiple times to ensure determinism @@ -495,6 +520,15 @@ mod tests { #[test] fn test_nonce_in_journal() { + let vault = [0x11; 20]; + let yield_source = [0x22; 20]; + let amount: u64 = 1000; + + let mut opaque_agent_inputs = Vec::with_capacity(48); + opaque_agent_inputs.extend_from_slice(&vault); + opaque_agent_inputs.extend_from_slice(&yield_source); + opaque_agent_inputs.extend_from_slice(&amount.to_le_bytes()); + let input1 = KernelInputV1 { protocol_version: PROTOCOL_VERSION, kernel_version: KERNEL_VERSION, @@ -503,7 +537,7 @@ mod tests { constraint_set_hash: [0xbb; 32], input_root: [0xcc; 32], execution_nonce: 1, - opaque_agent_inputs: vec![1, 2, 3], + opaque_agent_inputs: opaque_agent_inputs.clone(), }; let input2 = KernelInputV1 { @@ -524,7 +558,7 @@ mod tests { #[test] fn test_input_header_size() { // Verify minimum input size with empty data - let input = make_input(vec![]); + let input = make_input_raw(vec![]); let encoded = input.encode().unwrap(); // Fixed fields (144) + length prefix (4) + 0 bytes data = 148 @@ -955,7 +989,7 @@ mod tests { #[test] fn test_kernel_main_success_status() { // Normal execution should produce Success status - let input = make_input(vec![1, 2, 3]); + let input = make_input([0x11; 20], [0x22; 20], 1000); let input_bytes = input.encode().unwrap(); let journal_bytes = kernel_main(&input_bytes).unwrap(); @@ -967,9 +1001,9 @@ mod tests { #[test] fn test_kernel_main_with_constraints_success() { use constraints::ConstraintSetV1; - use kernel_guest::kernel_main_with_constraints; + use kernel_guest_binding_yield::kernel_main_with_constraints; - let input = make_input(vec![1, 2, 3]); + let input = make_input([0x11; 20], [0x22; 20], 1000); let input_bytes = input.encode().unwrap(); let constraints = ConstraintSetV1::default(); @@ -1036,15 +1070,31 @@ mod tests { } #[test] - fn test_enforce_constraints_echo_action() { - use constraints::{enforce_constraints, ConstraintSetV1, ACTION_TYPE_ECHO}; + fn test_enforce_constraints_call_action() { + use constraints::{enforce_constraints, ConstraintSetV1, ACTION_TYPE_CALL}; + + let input = make_input_raw(vec![]); + + // Create a valid CALL action with proper ABI encoding + // Target: EVM address (upper 12 bytes must be zero) + let mut target = [0u8; 32]; + target[12..32].copy_from_slice(&[0x11u8; 20]); + + // Payload: abi.encode(uint256 value, bytes callData) + // For empty calldata: value (32) + offset=64 (32) + length=0 (32) = 96 bytes + let mut payload = Vec::with_capacity(96); + payload.extend_from_slice(&[0u8; 32]); // value = 0 + // offset = 64 (big-endian u256) + payload.extend_from_slice(&[0u8; 31]); + payload.push(64); + // length = 0 (big-endian u256) + payload.extend_from_slice(&[0u8; 32]); - let input = make_input(vec![]); let output = AgentOutput { actions: vec![ActionV1 { - action_type: ACTION_TYPE_ECHO, - target: [0x11; 32], - payload: vec![1, 2, 3], + action_type: ACTION_TYPE_CALL, + target, + payload, }], }; let constraints = ConstraintSetV1::default(); @@ -1057,7 +1107,7 @@ mod tests { fn test_enforce_constraints_unknown_action_type() { use constraints::{enforce_constraints, ConstraintSetV1}; - let input = make_input(vec![]); + let input = make_input_raw(vec![]); let output = AgentOutput { actions: vec![ActionV1 { action_type: 0xFFFFFFFF, // Unknown type @@ -1076,15 +1126,15 @@ mod tests { #[test] fn test_enforce_constraints_too_many_actions() { - use constraints::{enforce_constraints, ConstraintSetV1, ACTION_TYPE_ECHO}; + use constraints::{enforce_constraints, ConstraintSetV1, ACTION_TYPE_CALL}; - let input = make_input(vec![]); + let input = make_input_raw(vec![]); let output = AgentOutput { actions: vec![ ActionV1 { - action_type: ACTION_TYPE_ECHO, + action_type: ACTION_TYPE_CALL, target: [0x11; 32], - payload: vec![1], + payload: vec![0u8; 96], }; 65 // 65 actions, max is 64 ], @@ -1097,157 +1147,6 @@ mod tests { assert_eq!(violation.reason, ConstraintViolationReason::InvalidOutputStructure); } - #[test] - fn test_enforce_constraints_position_too_large() { - use constraints::{enforce_constraints, ConstraintSetV1, ACTION_TYPE_OPEN_POSITION}; - - let input = make_input(vec![]); - - // Build OpenPosition payload - let mut payload = Vec::with_capacity(45); - payload.extend_from_slice(&[0x42; 32]); // asset_id - payload.extend_from_slice(&1_000_001u64.to_le_bytes()); // notional (exceeds limit) - payload.extend_from_slice(&10_000u32.to_le_bytes()); // leverage_bps (1x) - payload.push(0); // direction - - let output = AgentOutput { - actions: vec![ActionV1 { - action_type: ACTION_TYPE_OPEN_POSITION, - target: [0x22; 32], - payload, - }], - }; - - let constraints = ConstraintSetV1 { - max_position_notional: 1_000_000, - ..ConstraintSetV1::default() - }; - - let result = enforce_constraints(&input, &output, &constraints); - assert!(result.is_err()); - let violation = result.unwrap_err(); - assert_eq!(violation.reason, ConstraintViolationReason::PositionTooLarge); - } - - #[test] - fn test_enforce_constraints_leverage_too_high() { - use constraints::{enforce_constraints, ConstraintSetV1, ACTION_TYPE_OPEN_POSITION}; - - let input = make_input(vec![]); - - // Build OpenPosition payload with 10x leverage - let mut payload = Vec::with_capacity(45); - payload.extend_from_slice(&[0x42; 32]); // asset_id - payload.extend_from_slice(&1_000u64.to_le_bytes()); // notional - payload.extend_from_slice(&100_000u32.to_le_bytes()); // leverage_bps (10x) - payload.push(0); // direction - - let output = AgentOutput { - actions: vec![ActionV1 { - action_type: ACTION_TYPE_OPEN_POSITION, - target: [0x22; 32], - payload, - }], - }; - - let constraints = ConstraintSetV1 { - max_leverage_bps: 50_000, // 5x max - ..ConstraintSetV1::default() - }; - - let result = enforce_constraints(&input, &output, &constraints); - assert!(result.is_err()); - let violation = result.unwrap_err(); - assert_eq!(violation.reason, ConstraintViolationReason::LeverageTooHigh); - } - - #[test] - fn test_enforce_constraints_cooldown_not_elapsed() { - use constraints::{enforce_constraints, ConstraintSetV1, ACTION_TYPE_ECHO}; - - // Create input with state snapshot - let mut snapshot_bytes = Vec::new(); - snapshot_bytes.extend_from_slice(&1u32.to_le_bytes()); // version - snapshot_bytes.extend_from_slice(&1000u64.to_le_bytes()); // last_execution_ts - snapshot_bytes.extend_from_slice(&1030u64.to_le_bytes()); // current_ts (30s later) - snapshot_bytes.extend_from_slice(&100_000u64.to_le_bytes()); // current_equity - snapshot_bytes.extend_from_slice(&100_000u64.to_le_bytes()); // peak_equity - - let input = make_input(snapshot_bytes); - let output = AgentOutput { - actions: vec![ActionV1 { - action_type: ACTION_TYPE_ECHO, - target: [0x11; 32], - payload: vec![1], - }], - }; - - let constraints = ConstraintSetV1 { - cooldown_seconds: 60, // 60s cooldown - ..ConstraintSetV1::default() - }; - - let result = enforce_constraints(&input, &output, &constraints); - assert!(result.is_err()); - let violation = result.unwrap_err(); - assert_eq!(violation.reason, ConstraintViolationReason::CooldownNotElapsed); - assert_eq!(violation.action_index, None); // Global constraint - } - - #[test] - fn test_enforce_constraints_drawdown_exceeded() { - use constraints::{enforce_constraints, ConstraintSetV1, ACTION_TYPE_ECHO}; - - // Create input with state snapshot showing 30% drawdown - let mut snapshot_bytes = Vec::new(); - snapshot_bytes.extend_from_slice(&1u32.to_le_bytes()); // version - snapshot_bytes.extend_from_slice(&1000u64.to_le_bytes()); // last_execution_ts - snapshot_bytes.extend_from_slice(&2000u64.to_le_bytes()); // current_ts - snapshot_bytes.extend_from_slice(&70_000u64.to_le_bytes()); // current_equity (70%) - snapshot_bytes.extend_from_slice(&100_000u64.to_le_bytes()); // peak_equity - - let input = make_input(snapshot_bytes); - let output = AgentOutput { - actions: vec![ActionV1 { - action_type: ACTION_TYPE_ECHO, - target: [0x11; 32], - payload: vec![1], - }], - }; - - let constraints = ConstraintSetV1 { - max_drawdown_bps: 2_000, // 20% max drawdown - ..ConstraintSetV1::default() - }; - - let result = enforce_constraints(&input, &output, &constraints); - assert!(result.is_err()); - let violation = result.unwrap_err(); - assert_eq!(violation.reason, ConstraintViolationReason::DrawdownExceeded); - assert_eq!(violation.action_index, None); // Global constraint - } - - #[test] - fn test_enforce_constraints_invalid_payload() { - use constraints::{enforce_constraints, ConstraintSetV1, ACTION_TYPE_OPEN_POSITION}; - - let input = make_input(vec![]); - let output = AgentOutput { - actions: vec![ActionV1 { - action_type: ACTION_TYPE_OPEN_POSITION, - target: [0x22; 32], - payload: vec![1, 2, 3], // Too short for OpenPosition (needs 45 bytes) - }], - }; - - let constraints = ConstraintSetV1::default(); - - let result = enforce_constraints(&input, &output, &constraints); - assert!(result.is_err()); - let violation = result.unwrap_err(); - assert_eq!(violation.reason, ConstraintViolationReason::InvalidActionPayload); - } - #[test] fn test_journal_failure_has_empty_commitment() { use constraints::EMPTY_OUTPUT_COMMITMENT; @@ -1274,258 +1173,20 @@ mod tests { assert_eq!(decoded.action_commitment, EMPTY_OUTPUT_COMMITMENT); } - // ======================================================================== - // P0.3: Missing Snapshot Safety Tests - // ======================================================================== - - #[test] - fn test_missing_snapshot_with_cooldown_enabled_fails() { - use constraints::{enforce_constraints, ConstraintSetV1, ACTION_TYPE_ECHO}; - - // No snapshot provided (empty opaque_agent_inputs) - let input = make_input(vec![]); - let output = AgentOutput { - actions: vec![ActionV1 { - action_type: ACTION_TYPE_ECHO, - target: [0x11; 32], - payload: vec![1], - }], - }; - - // Enable cooldown constraint - let constraints = ConstraintSetV1 { - cooldown_seconds: 60, // Cooldown enabled - ..ConstraintSetV1::default() - }; - - let result = enforce_constraints(&input, &output, &constraints); - assert!(result.is_err()); - let violation = result.unwrap_err(); - assert_eq!(violation.reason, ConstraintViolationReason::InvalidStateSnapshot); - assert_eq!(violation.action_index, None); // Global constraint - } - - #[test] - fn test_missing_snapshot_with_drawdown_enabled_fails() { - use constraints::{enforce_constraints, ConstraintSetV1, ACTION_TYPE_ECHO}; - - // No snapshot provided (empty opaque_agent_inputs) - let input = make_input(vec![]); - let output = AgentOutput { - actions: vec![ActionV1 { - action_type: ACTION_TYPE_ECHO, - target: [0x11; 32], - payload: vec![1], - }], - }; - - // Enable drawdown constraint (< 100%) - let constraints = ConstraintSetV1 { - max_drawdown_bps: 2_000, // 20% max drawdown (enabled) - ..ConstraintSetV1::default() - }; - - let result = enforce_constraints(&input, &output, &constraints); - assert!(result.is_err()); - let violation = result.unwrap_err(); - assert_eq!(violation.reason, ConstraintViolationReason::InvalidStateSnapshot); - assert_eq!(violation.action_index, None); // Global constraint - } - - #[test] - fn test_missing_snapshot_with_disabled_constraints_passes() { - use constraints::{enforce_constraints, ConstraintSetV1, ACTION_TYPE_ECHO}; - - // No snapshot provided (empty opaque_agent_inputs) - let input = make_input(vec![]); - let output = AgentOutput { - actions: vec![ActionV1 { - action_type: ACTION_TYPE_ECHO, - target: [0x11; 32], - payload: vec![1], - }], - }; - - // Both cooldown and drawdown disabled (default values) - let constraints = ConstraintSetV1 { - cooldown_seconds: 0, // Disabled - max_drawdown_bps: 10_000, // 100% = disabled - ..ConstraintSetV1::default() - }; - - let result = enforce_constraints(&input, &output, &constraints); - assert!(result.is_ok()); - } - - #[test] - fn test_drawdown_with_equity_growth_passes() { - use constraints::{enforce_constraints, ConstraintSetV1, ACTION_TYPE_ECHO}; - - // Create input with state snapshot where current_equity > peak_equity - let mut snapshot_bytes = Vec::new(); - snapshot_bytes.extend_from_slice(&1u32.to_le_bytes()); // version - snapshot_bytes.extend_from_slice(&1000u64.to_le_bytes()); // last_execution_ts - snapshot_bytes.extend_from_slice(&2000u64.to_le_bytes()); // current_ts - snapshot_bytes.extend_from_slice(&120_000u64.to_le_bytes()); // current_equity (120% of peak) - snapshot_bytes.extend_from_slice(&100_000u64.to_le_bytes()); // peak_equity - - let input = make_input(snapshot_bytes); - let output = AgentOutput { - actions: vec![ActionV1 { - action_type: ACTION_TYPE_ECHO, - target: [0x11; 32], - payload: vec![1], - }], - }; - - // Enable strict drawdown constraint - let constraints = ConstraintSetV1 { - max_drawdown_bps: 500, // 5% max drawdown (strict) - ..ConstraintSetV1::default() - }; - - // Should pass because current_equity > peak_equity means 0 drawdown - let result = enforce_constraints(&input, &output, &constraints); - assert!(result.is_ok()); - } - - // ======================================================================== - // P0.3: Payload Trailing Bytes Rejection Tests - // ======================================================================== - - #[test] - fn test_open_position_payload_trailing_bytes_rejected() { - use constraints::{enforce_constraints, ConstraintSetV1, ACTION_TYPE_OPEN_POSITION}; - - let input = make_input(vec![]); - - // Build valid OpenPosition payload (45 bytes) then add trailing byte - let mut payload = Vec::with_capacity(46); - payload.extend_from_slice(&[0x42; 32]); // asset_id - payload.extend_from_slice(&1000u64.to_le_bytes()); // notional - payload.extend_from_slice(&10_000u32.to_le_bytes()); // leverage_bps (1x) - payload.push(0); // direction - payload.push(0xFF); // TRAILING BYTE - - let output = AgentOutput { - actions: vec![ActionV1 { - action_type: ACTION_TYPE_OPEN_POSITION, - target: [0x22; 32], - payload, - }], - }; - - let constraints = ConstraintSetV1::default(); - let result = enforce_constraints(&input, &output, &constraints); - - assert!(result.is_err()); - let violation = result.unwrap_err(); - assert_eq!(violation.reason, ConstraintViolationReason::InvalidActionPayload); - assert_eq!(violation.action_index, Some(0)); - } - - #[test] - fn test_close_position_payload_trailing_bytes_rejected() { - use constraints::{enforce_constraints, ConstraintSetV1, ACTION_TYPE_CLOSE_POSITION}; - - let input = make_input(vec![]); - - // Build valid ClosePosition payload (32 bytes) then add trailing byte - let mut payload = Vec::with_capacity(33); - payload.extend_from_slice(&[0xab; 32]); // position_id - payload.push(0xFF); // TRAILING BYTE - - let output = AgentOutput { - actions: vec![ActionV1 { - action_type: ACTION_TYPE_CLOSE_POSITION, - target: [0x33; 32], - payload, - }], - }; - - let constraints = ConstraintSetV1::default(); - let result = enforce_constraints(&input, &output, &constraints); - - assert!(result.is_err()); - let violation = result.unwrap_err(); - assert_eq!(violation.reason, ConstraintViolationReason::InvalidActionPayload); - assert_eq!(violation.action_index, Some(0)); - } - - #[test] - fn test_adjust_position_payload_trailing_bytes_rejected() { - use constraints::{enforce_constraints, ConstraintSetV1, ACTION_TYPE_ADJUST_POSITION}; - - let input = make_input(vec![]); - - // Build valid AdjustPosition payload (44 bytes) then add trailing byte - let mut payload = Vec::with_capacity(45); - payload.extend_from_slice(&[0xab; 32]); // position_id - payload.extend_from_slice(&2000u64.to_le_bytes()); // new_notional - payload.extend_from_slice(&20_000u32.to_le_bytes()); // new_leverage_bps - payload.push(0xFF); // TRAILING BYTE - - let output = AgentOutput { - actions: vec![ActionV1 { - action_type: ACTION_TYPE_ADJUST_POSITION, - target: [0x44; 32], - payload, - }], - }; - - let constraints = ConstraintSetV1::default(); - let result = enforce_constraints(&input, &output, &constraints); - - assert!(result.is_err()); - let violation = result.unwrap_err(); - assert_eq!(violation.reason, ConstraintViolationReason::InvalidActionPayload); - assert_eq!(violation.action_index, Some(0)); - } - - #[test] - fn test_swap_payload_trailing_bytes_rejected() { - use constraints::{enforce_constraints, ConstraintSetV1, ACTION_TYPE_SWAP}; - - let input = make_input(vec![]); - - // Build valid Swap payload (72 bytes) then add trailing byte - let mut payload = Vec::with_capacity(73); - payload.extend_from_slice(&[0xaa; 32]); // from_asset - payload.extend_from_slice(&[0xbb; 32]); // to_asset - payload.extend_from_slice(&1000u64.to_le_bytes()); // amount - payload.push(0xFF); // TRAILING BYTE - - let output = AgentOutput { - actions: vec![ActionV1 { - action_type: ACTION_TYPE_SWAP, - target: [0x55; 32], - payload, - }], - }; - - let constraints = ConstraintSetV1::default(); - let result = enforce_constraints(&input, &output, &constraints); - - assert!(result.is_err()); - let violation = result.unwrap_err(); - assert_eq!(violation.reason, ConstraintViolationReason::InvalidActionPayload); - assert_eq!(violation.action_index, Some(0)); - } - // ======================================================================== // P0.3: ConstraintSet Invariant Validation Tests // ======================================================================== #[test] fn test_invalid_constraint_set_max_actions_too_large() { - use constraints::{enforce_constraints, ConstraintSetV1, ACTION_TYPE_ECHO}; + use constraints::{enforce_constraints, ConstraintSetV1, ACTION_TYPE_CALL}; - let input = make_input(vec![]); + let input = make_input_raw(vec![]); let output = AgentOutput { actions: vec![ActionV1 { - action_type: ACTION_TYPE_ECHO, + action_type: ACTION_TYPE_CALL, target: [0x11; 32], - payload: vec![1], + payload: vec![0u8; 96], }], }; @@ -1544,14 +1205,14 @@ mod tests { #[test] fn test_invalid_constraint_set_drawdown_too_large() { - use constraints::{enforce_constraints, ConstraintSetV1, ACTION_TYPE_ECHO}; + use constraints::{enforce_constraints, ConstraintSetV1, ACTION_TYPE_CALL}; - let input = make_input(vec![]); + let input = make_input_raw(vec![]); let output = AgentOutput { actions: vec![ActionV1 { - action_type: ACTION_TYPE_ECHO, + action_type: ACTION_TYPE_CALL, target: [0x11; 32], - payload: vec![1], + payload: vec![0u8; 96], }], }; @@ -1568,247 +1229,13 @@ mod tests { assert_eq!(violation.action_index, None); // Global constraint } - #[test] - fn test_valid_constraint_set_zero_leverage() { - use constraints::{enforce_constraints, ConstraintSetV1, ACTION_TYPE_ECHO}; - - let input = make_input(vec![]); - let output = AgentOutput { - actions: vec![ActionV1 { - action_type: ACTION_TYPE_ECHO, - target: [0x11; 32], - payload: vec![1], - }], - }; - - // max_leverage_bps == 0 is valid (would reject all leveraged positions) - let constraints = ConstraintSetV1 { - max_leverage_bps: 0, - ..ConstraintSetV1::default() - }; - - let result = enforce_constraints(&input, &output, &constraints); - // Should pass for echo action (no leverage check) - assert!(result.is_ok()); - } - - // ======================================================================== - // P0.3: Asset Whitelist Tests - // ======================================================================== - - #[test] - fn test_open_position_asset_not_whitelisted_fails() { - use constraints::{enforce_constraints, ConstraintSetV1, ACTION_TYPE_OPEN_POSITION}; - - let input = make_input(vec![]); - - // Build OpenPosition payload with asset_id = [0x99; 32] - let mut payload = Vec::with_capacity(45); - payload.extend_from_slice(&[0x99; 32]); // asset_id (NOT whitelisted) - payload.extend_from_slice(&1000u64.to_le_bytes()); // notional - payload.extend_from_slice(&10_000u32.to_le_bytes()); // leverage_bps (1x) - payload.push(0); // direction - - let output = AgentOutput { - actions: vec![ActionV1 { - action_type: ACTION_TYPE_OPEN_POSITION, - target: [0x22; 32], - payload, - }], - }; - - // Whitelist only allows [0x42; 32] - let constraints = ConstraintSetV1 { - allowed_asset_id: [0x42; 32], - ..ConstraintSetV1::default() - }; - - let result = enforce_constraints(&input, &output, &constraints); - assert!(result.is_err()); - let violation = result.unwrap_err(); - assert_eq!(violation.reason, ConstraintViolationReason::AssetNotWhitelisted); - assert_eq!(violation.action_index, Some(0)); - } - - #[test] - fn test_swap_from_asset_not_whitelisted_fails() { - use constraints::{enforce_constraints, ConstraintSetV1, ACTION_TYPE_SWAP}; - - let input = make_input(vec![]); - - // Build Swap payload with from_asset = [0x99; 32] (not whitelisted) - let mut payload = Vec::with_capacity(72); - payload.extend_from_slice(&[0x99; 32]); // from_asset (NOT whitelisted) - payload.extend_from_slice(&[0x42; 32]); // to_asset (whitelisted) - payload.extend_from_slice(&1000u64.to_le_bytes()); // amount - - let output = AgentOutput { - actions: vec![ActionV1 { - action_type: ACTION_TYPE_SWAP, - target: [0x55; 32], - payload, - }], - }; - - // Whitelist only allows [0x42; 32] - let constraints = ConstraintSetV1 { - allowed_asset_id: [0x42; 32], - ..ConstraintSetV1::default() - }; - - let result = enforce_constraints(&input, &output, &constraints); - assert!(result.is_err()); - let violation = result.unwrap_err(); - assert_eq!(violation.reason, ConstraintViolationReason::AssetNotWhitelisted); - assert_eq!(violation.action_index, Some(0)); - } - - #[test] - fn test_swap_to_asset_not_whitelisted_fails() { - use constraints::{enforce_constraints, ConstraintSetV1, ACTION_TYPE_SWAP}; - - let input = make_input(vec![]); - - // Build Swap payload with to_asset = [0x99; 32] (not whitelisted) - let mut payload = Vec::with_capacity(72); - payload.extend_from_slice(&[0x42; 32]); // from_asset (whitelisted) - payload.extend_from_slice(&[0x99; 32]); // to_asset (NOT whitelisted) - payload.extend_from_slice(&1000u64.to_le_bytes()); // amount - - let output = AgentOutput { - actions: vec![ActionV1 { - action_type: ACTION_TYPE_SWAP, - target: [0x55; 32], - payload, - }], - }; - - // Whitelist only allows [0x42; 32] - let constraints = ConstraintSetV1 { - allowed_asset_id: [0x42; 32], - ..ConstraintSetV1::default() - }; - - let result = enforce_constraints(&input, &output, &constraints); - assert!(result.is_err()); - let violation = result.unwrap_err(); - assert_eq!(violation.reason, ConstraintViolationReason::AssetNotWhitelisted); - assert_eq!(violation.action_index, Some(0)); - } - - #[test] - fn test_allowed_asset_id_zero_allows_any_asset() { - use constraints::{enforce_constraints, ConstraintSetV1, ACTION_TYPE_OPEN_POSITION}; - - let input = make_input(vec![]); - - // Build OpenPosition payload with arbitrary asset_id - let mut payload = Vec::with_capacity(45); - payload.extend_from_slice(&[0x99; 32]); // asset_id (any value) - payload.extend_from_slice(&1000u64.to_le_bytes()); // notional - payload.extend_from_slice(&10_000u32.to_le_bytes()); // leverage_bps (1x) - payload.push(0); // direction - - let output = AgentOutput { - actions: vec![ActionV1 { - action_type: ACTION_TYPE_OPEN_POSITION, - target: [0x22; 32], - payload, - }], - }; - - // allowed_asset_id = [0; 32] means all assets are allowed - let constraints = ConstraintSetV1 { - allowed_asset_id: [0u8; 32], // Zero = all assets allowed - ..ConstraintSetV1::default() - }; - - let result = enforce_constraints(&input, &output, &constraints); - // Should pass - zero allowed_asset_id means no whitelist restriction - assert!(result.is_ok()); - } - // ======================================================================== - // P0.3: Cooldown Timestamp Overflow Tests + // P0.4: Yield Agent Tests // ======================================================================== #[test] - fn test_cooldown_timestamp_overflow_rejected() { - use constraints::{enforce_constraints, ConstraintSetV1, ACTION_TYPE_ECHO}; - - // Create input with state snapshot where last_execution_ts is near u64::MAX - let mut snapshot_bytes = Vec::new(); - snapshot_bytes.extend_from_slice(&1u32.to_le_bytes()); // version - snapshot_bytes.extend_from_slice(&u64::MAX.to_le_bytes()); // last_execution_ts (malicious) - snapshot_bytes.extend_from_slice(&1000u64.to_le_bytes()); // current_ts - snapshot_bytes.extend_from_slice(&100_000u64.to_le_bytes()); // current_equity - snapshot_bytes.extend_from_slice(&100_000u64.to_le_bytes()); // peak_equity - - let input = make_input(snapshot_bytes); - let output = AgentOutput { - actions: vec![ActionV1 { - action_type: ACTION_TYPE_ECHO, - target: [0x11; 32], - payload: vec![1], - }], - }; - - // Enable cooldown constraint - let constraints = ConstraintSetV1 { - cooldown_seconds: 60, - ..ConstraintSetV1::default() - }; - - let result = enforce_constraints(&input, &output, &constraints); - assert!(result.is_err()); - let violation = result.unwrap_err(); - // Overflow should result in InvalidStateSnapshot, not bypass cooldown - assert_eq!(violation.reason, ConstraintViolationReason::InvalidStateSnapshot); - assert_eq!(violation.action_index, None); - } - - #[test] - fn test_cooldown_timestamp_near_overflow_boundary() { - use constraints::{enforce_constraints, ConstraintSetV1, ACTION_TYPE_ECHO}; - - // last_execution_ts + cooldown_seconds would overflow - let malicious_ts = u64::MAX - 30; // Will overflow when adding 60 - - let mut snapshot_bytes = Vec::new(); - snapshot_bytes.extend_from_slice(&1u32.to_le_bytes()); // version - snapshot_bytes.extend_from_slice(&malicious_ts.to_le_bytes()); // last_execution_ts - snapshot_bytes.extend_from_slice(&1000u64.to_le_bytes()); // current_ts - snapshot_bytes.extend_from_slice(&100_000u64.to_le_bytes()); // current_equity - snapshot_bytes.extend_from_slice(&100_000u64.to_le_bytes()); // peak_equity - - let input = make_input(snapshot_bytes); - let output = AgentOutput { - actions: vec![ActionV1 { - action_type: ACTION_TYPE_ECHO, - target: [0x11; 32], - payload: vec![1], - }], - }; - - let constraints = ConstraintSetV1 { - cooldown_seconds: 60, - ..ConstraintSetV1::default() - }; - - let result = enforce_constraints(&input, &output, &constraints); - assert!(result.is_err()); - let violation = result.unwrap_err(); - assert_eq!(violation.reason, ConstraintViolationReason::InvalidStateSnapshot); - } - - // ======================================================================== - // P0.4: Canonical agent_main Entrypoint Tests - // ======================================================================== - - #[test] - fn test_agent_main_echo_success() { - // When opaque_inputs[0] == 1, the example-agent produces an echo action - // which should result in SUCCESS status + fn test_yield_agent_valid_input_success() { + // When yield agent receives valid 48-byte input, it produces 2 CALL actions let input = KernelInputV1 { protocol_version: PROTOCOL_VERSION, kernel_version: KERNEL_VERSION, @@ -1817,7 +1244,7 @@ mod tests { constraint_set_hash: [0xbb; 32], input_root: [0xcc; 32], execution_nonce: 1, - opaque_agent_inputs: vec![1, 2, 3, 4, 5], // First byte is 1 -> echo + opaque_agent_inputs: vec![0u8; 48], // Valid 48-byte input }; let input_bytes = input.encode().unwrap(); @@ -1834,14 +1261,13 @@ mod tests { assert_eq!(journal.input_root, [0xcc; 32]); assert_eq!(journal.execution_nonce, 1); - // Action commitment should NOT be the empty output commitment + // Action commitment should NOT be the empty output commitment (agent produces 2 actions) assert_ne!(journal.action_commitment, EMPTY_OUTPUT_COMMITMENT); } #[test] - fn test_agent_main_no_echo_empty_output() { - // When opaque_inputs[0] != 1, the example-agent produces no actions - // which should still result in SUCCESS status (empty output is valid) + fn test_yield_agent_invalid_input_empty_output() { + // When yield agent receives wrong-sized input, it produces no actions let input = KernelInputV1 { protocol_version: PROTOCOL_VERSION, kernel_version: KERNEL_VERSION, @@ -1850,7 +1276,7 @@ mod tests { constraint_set_hash: [0xbb; 32], input_root: [0xcc; 32], execution_nonce: 2, - opaque_agent_inputs: vec![0, 2, 3, 4, 5], // First byte is 0 -> no echo + opaque_agent_inputs: vec![0, 2, 3, 4, 5], // Only 5 bytes - invalid }; let input_bytes = input.encode().unwrap(); @@ -1865,71 +1291,7 @@ mod tests { } #[test] - fn test_agent_main_empty_inputs_no_echo() { - // Empty opaque_inputs should result in no actions - let input = KernelInputV1 { - protocol_version: PROTOCOL_VERSION, - kernel_version: KERNEL_VERSION, - agent_id: [0x42; 32], - agent_code_hash: AGENT_CODE_HASH, // Must match linked agent - constraint_set_hash: [0xbb; 32], - input_root: [0xcc; 32], - execution_nonce: 3, - opaque_agent_inputs: vec![], // Empty -> no echo - }; - - let input_bytes = input.encode().unwrap(); - let journal_bytes = kernel_main(&input_bytes).unwrap(); - let journal = KernelJournalV1::decode(&journal_bytes).unwrap(); - - // Verify SUCCESS status - assert_eq!(journal.execution_status, ExecutionStatus::Success); - - // Action commitment should be the empty output commitment - assert_eq!(journal.action_commitment, EMPTY_OUTPUT_COMMITMENT); - } - - #[test] - fn test_agent_main_echo_commitment_verification() { - // Verify that the action commitment is correctly computed for echo action - let input = KernelInputV1 { - protocol_version: PROTOCOL_VERSION, - kernel_version: KERNEL_VERSION, - agent_id: [0x42; 32], - agent_code_hash: AGENT_CODE_HASH, // Must match linked agent - constraint_set_hash: [0xbb; 32], - input_root: [0xcc; 32], - execution_nonce: 4, - opaque_agent_inputs: vec![1, 0xde, 0xad, 0xbe, 0xef], // First byte is 1 -> echo - }; - - let input_bytes = input.encode().unwrap(); - let journal_bytes = kernel_main(&input_bytes).unwrap(); - let journal = KernelJournalV1::decode(&journal_bytes).unwrap(); - - assert_eq!(journal.execution_status, ExecutionStatus::Success); - - // Manually compute expected action commitment - // The example-agent creates an echo action with: - // - action_type = 0x00000001 (ECHO) - // - target = agent_id = [0x42; 32] - // - payload = opaque_inputs = [1, 0xde, 0xad, 0xbe, 0xef] - let expected_action = ActionV1 { - action_type: 0x00000001, // ACTION_TYPE_ECHO - target: [0x42; 32], - payload: vec![1, 0xde, 0xad, 0xbe, 0xef], - }; - let expected_output = AgentOutput { - actions: vec![expected_action], - }; - let expected_output_bytes = expected_output.encode().unwrap(); - let expected_commitment = compute_action_commitment(&expected_output_bytes); - - assert_eq!(journal.action_commitment, expected_commitment); - } - - #[test] - fn test_agent_main_determinism() { + fn test_yield_agent_determinism() { // Verify that the same input produces the same output let input = KernelInputV1 { protocol_version: PROTOCOL_VERSION, @@ -1939,7 +1301,7 @@ mod tests { constraint_set_hash: [0x33; 32], input_root: [0x44; 32], execution_nonce: 100, - opaque_agent_inputs: vec![1, 0xff, 0xee, 0xdd], + opaque_agent_inputs: vec![0u8; 48], // Valid 48-byte input }; let input_bytes = input.encode().unwrap(); @@ -1955,11 +1317,16 @@ mod tests { } #[test] - fn test_constraint_violation_with_agent_main() { - // Test that constraint violations are properly handled with the new agent_main flow - // Use a constraint set that rejects the echo action by limiting max_actions_per_output to 0 - use constraints::ConstraintSetV1; - use kernel_guest::kernel_main_with_constraints; + fn test_yield_agent_action_commitment_verification() { + // Verify that the action commitment is correctly computed for yield agent + let vault = [0x11u8; 20]; + let yield_source = [0x22u8; 20]; + let amount: u64 = 1_000_000_000_000_000_000; // 1 ETH + + let mut opaque_inputs = Vec::with_capacity(48); + opaque_inputs.extend_from_slice(&vault); + opaque_inputs.extend_from_slice(&yield_source); + opaque_inputs.extend_from_slice(&amount.to_le_bytes()); let input = KernelInputV1 { protocol_version: PROTOCOL_VERSION, @@ -1968,25 +1335,36 @@ mod tests { agent_code_hash: AGENT_CODE_HASH, // Must match linked agent constraint_set_hash: [0xbb; 32], input_root: [0xcc; 32], - execution_nonce: 5, - opaque_agent_inputs: vec![1, 2, 3], // First byte is 1 -> echo (produces 1 action) - }; - - // Constraint set that only allows 0 actions - let constraints = ConstraintSetV1 { - max_actions_per_output: 0, // This will reject any non-empty output - ..ConstraintSetV1::default() + execution_nonce: 4, + opaque_agent_inputs: opaque_inputs, }; let input_bytes = input.encode().unwrap(); - let journal_bytes = kernel_main_with_constraints(&input_bytes, &constraints).unwrap(); + let journal_bytes = kernel_main(&input_bytes).unwrap(); let journal = KernelJournalV1::decode(&journal_bytes).unwrap(); - // Verify FAILURE status due to constraint violation - assert_eq!(journal.execution_status, ExecutionStatus::Failure); + assert_eq!(journal.execution_status, ExecutionStatus::Success); - // On failure, action commitment should be the empty output commitment - assert_eq!(journal.action_commitment, EMPTY_OUTPUT_COMMITMENT); + // Manually compute expected action commitment + let target = address_to_bytes32(&yield_source); + + // Deposit action + let deposit_action = call_action(target, amount as u128, &[]); + + // Withdraw action + let withdraw_selector: [u8; 4] = [0x51, 0xcf, 0xf8, 0xd9]; + let mut withdraw_calldata = Vec::with_capacity(36); + withdraw_calldata.extend_from_slice(&withdraw_selector); + withdraw_calldata.extend_from_slice(&address_to_bytes32(&vault)); + let withdraw_action = call_action(target, 0, &withdraw_calldata); + + let expected_output = AgentOutput { + actions: vec![deposit_action, withdraw_action], + }; + let expected_output_bytes = expected_output.encode().unwrap(); + let expected_commitment = compute_action_commitment(&expected_output_bytes); + + assert_eq!(journal.action_commitment, expected_commitment); } // ======================================================================== @@ -1996,8 +1374,7 @@ mod tests { #[test] fn test_agent_code_hash_match_passes() { // When agent_code_hash matches the linked agent, execution succeeds. - // This test verifies that the hash binding works correctly. - let input = make_input(vec![1, 2, 3, 4, 5]); // Echo input (make_input uses correct hash) + let input = make_input([0x11; 20], [0x22; 20], 1000); let input_bytes = input.encode().unwrap(); let result = kernel_main(&input_bytes); @@ -2018,9 +1395,13 @@ mod tests { #[test] fn test_agent_code_hash_mismatch_fails() { // When agent_code_hash does NOT match the linked agent, execution fails. - // This test verifies that the kernel rejects mismatched hashes. let wrong_hash = [0xde; 32]; // Wrong hash + let mut opaque_inputs = Vec::with_capacity(48); + opaque_inputs.extend_from_slice(&[0x11; 20]); + opaque_inputs.extend_from_slice(&[0x22; 20]); + opaque_inputs.extend_from_slice(&1000u64.to_le_bytes()); + let input = KernelInputV1 { protocol_version: PROTOCOL_VERSION, kernel_version: KERNEL_VERSION, @@ -2029,7 +1410,7 @@ mod tests { constraint_set_hash: [0xbb; 32], input_root: [0xcc; 32], execution_nonce: 1, - opaque_agent_inputs: vec![1, 2, 3], // Echo input + opaque_agent_inputs: opaque_inputs, }; let input_bytes = input.encode().unwrap(); @@ -2048,16 +1429,21 @@ mod tests { fn test_agent_code_hash_with_constraints() { // Test that agent code hash verification works with custom constraints too. use constraints::ConstraintSetV1; - use kernel_guest::kernel_main_with_constraints; + use kernel_guest_binding_yield::kernel_main_with_constraints; // With valid hash - should succeed - let valid_input = make_input(vec![1, 2, 3]); // make_input uses correct hash + let valid_input = make_input([0x11; 20], [0x22; 20], 1000); let constraints = ConstraintSetV1::default(); let input_bytes = valid_input.encode().unwrap(); let result = kernel_main_with_constraints(&input_bytes, &constraints); assert!(result.is_ok(), "Expected success with valid hash"); // With invalid hash - should fail + let mut opaque_inputs = Vec::with_capacity(48); + opaque_inputs.extend_from_slice(&[0x11; 20]); + opaque_inputs.extend_from_slice(&[0x22; 20]); + opaque_inputs.extend_from_slice(&1000u64.to_le_bytes()); + let invalid_input = KernelInputV1 { protocol_version: PROTOCOL_VERSION, kernel_version: KERNEL_VERSION, @@ -2066,7 +1452,7 @@ mod tests { constraint_set_hash: [0xbb; 32], input_root: [0xcc; 32], execution_nonce: 2, - opaque_agent_inputs: vec![1, 2, 3], + opaque_agent_inputs: opaque_inputs, }; let input_bytes = invalid_input.encode().unwrap(); let result = kernel_main_with_constraints(&input_bytes, &constraints); @@ -2080,8 +1466,6 @@ mod tests { #[test] fn test_agent_code_hash_constant_is_stable() { // Verify the agent code hash is a 32-byte value (sanity check). - // The actual hash value will change when agent source changes, - // but the format should always be 32 bytes. assert_eq!(AGENT_CODE_HASH.len(), 32); // Hash should not be all zeros (would indicate a problem) diff --git a/scripts/print_registration_values.sh b/scripts/print_registration_values.sh new file mode 100755 index 0000000..c3abc16 --- /dev/null +++ b/scripts/print_registration_values.sh @@ -0,0 +1,53 @@ +#!/bin/bash +# Print registration values for on-chain deployment +# +# Usage: ./scripts/print_registration_values.sh + +set -e + +echo "=== Building methods crate with yield agent ===" +echo "" + +# Ensure zkvm-guest uses yield agent +echo "Verifying zkvm-guest/Cargo.toml uses example-yield-agent..." +grep -q "example-yield-agent" crates/methods/zkvm-guest/Cargo.toml || { + echo "ERROR: zkvm-guest/Cargo.toml should use example-yield-agent feature" + exit 1 +} + +echo "Building methods crate (this may take a while for first build)..." +cargo build --release -p methods 2>&1 | tail -5 + +echo "" +echo "=== Registration Values ===" +echo "" + +# Print agent code hash +echo "1. AGENT_CODE_HASH (for KernelInputV1.agent_code_hash):" +cargo build -p example-yield-agent 2>&1 | grep "AGENT_CODE_HASH" | sed 's/.*: / 0x/' + +echo "" +echo "2. IMAGE_ID (for registerAgent on KernelExecutionVerifier):" +echo " Run the e2e test to get this value:" +echo " cargo test -p e2e-tests --features risc0-e2e test_e2e -- --nocapture 2>&1 | grep 'image_id'" +echo "" +echo " Or print from Rust:" +echo " use methods::ZKVM_GUEST_ID;" +echo " let bytes: Vec = ZKVM_GUEST_ID.iter().flat_map(|x| x.to_le_bytes()).collect();" +echo " println!(\"0x{}\", hex::encode(&bytes));" + +echo "" +echo "3. AGENT_ID (for KernelVault constructor and KernelInputV1.agent_id):" +echo " This is any bytes32 you choose to identify this agent/vault pair." +echo " Example: 0x4242424242424242424242424242424242424242424242424242424242424242" + +echo "" +echo "=== On-Chain Registration Steps ===" +echo "" +echo "1. Deploy RISC Zero Verifier (or use existing one)" +echo "2. Deploy KernelExecutionVerifier(_verifier)" +echo "3. Call verifier.registerAgent(AGENT_ID, IMAGE_ID)" +echo "4. Deploy KernelVault(_asset, _verifier, AGENT_ID)" +echo "5. Deploy MockYieldSource(vaultAddress)" +echo "6. Fund MockYieldSource with ETH for yield (10% of expected deposits)" +echo "7. Fund KernelVault with ETH for test deposit" diff --git a/simple-ai-agent b/simple-ai-agent deleted file mode 160000 index 6942c12..0000000 --- a/simple-ai-agent +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 6942c12149a2dc2118097331ac3aa1a84b9ecbf3