Add Cross-program invocations (#9582)

This commit is contained in:
Jack May
2020-04-28 14:33:56 -07:00
committed by GitHub
parent 063f616a19
commit 068f12fd6f
27 changed files with 2164 additions and 208 deletions

53
Cargo.lock generated
View File

@ -43,11 +43,6 @@ name = "anyhow"
version = "1.0.26" version = "1.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "approx"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "ar" name = "ar"
version = "0.6.2" version = "0.6.2"
@ -472,16 +467,6 @@ name = "cfg-if"
version = "0.1.9" version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "cgmath"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"approx 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]] [[package]]
name = "chashmap" name = "chashmap"
version = "2.2.2" version = "2.2.2"
@ -573,11 +558,12 @@ dependencies = [
[[package]] [[package]]
name = "colored" name = "colored"
version = "1.8.0" version = "1.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"winconsole 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]
@ -1000,7 +986,7 @@ dependencies = [
"bloom 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "bloom 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
"clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
"colored 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "colored 1.9.3 (registry+https://github.com/rust-lang/crates.io-index)",
"enum-primitive-derive 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "enum-primitive-derive 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"env_logger 0.5.13 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.5.13 (registry+https://github.com/rust-lang/crates.io-index)",
"fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1397,7 +1383,7 @@ dependencies = [
[[package]] [[package]]
name = "hash32" name = "hash32"
version = "0.1.0" version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
@ -3151,11 +3137,6 @@ dependencies = [
"winreg 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", "winreg 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]]
name = "rgb"
version = "0.8.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "ring" name = "ring"
version = "0.16.11" version = "0.16.11"
@ -3755,11 +3736,12 @@ version = "1.2.0"
dependencies = [ dependencies = [
"bincode 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "bincode 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)", "jemalloc-sys 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"num-derive 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "num-derive 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
"solana-logger 1.2.0", "solana-logger 1.2.0",
"solana-runtime 1.2.0",
"solana-sdk 1.2.0", "solana-sdk 1.2.0",
"solana_rbpf 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)", "solana_rbpf 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)",
"thiserror 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)", "thiserror 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)",
@ -5161,7 +5143,7 @@ dependencies = [
"byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
"combine 2.5.2 (registry+https://github.com/rust-lang/crates.io-index)", "combine 2.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
"elfkit 0.0.6 (registry+https://github.com/rust-lang/crates.io-index)", "elfkit 0.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
"hash32 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "hash32 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
@ -6233,17 +6215,6 @@ dependencies = [
"winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]]
name = "winconsole"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cgmath 0.16.1 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"rgb 0.8.13 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]] [[package]]
name = "winreg" name = "winreg"
version = "0.6.2" version = "0.6.2"
@ -6346,7 +6317,6 @@ dependencies = [
"checksum aho-corasick 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)" = "58fb5e95d83b38284460a5fda7d6470aa0b8844d283a0b614b8535e880800d2d" "checksum aho-corasick 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)" = "58fb5e95d83b38284460a5fda7d6470aa0b8844d283a0b614b8535e880800d2d"
"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" "checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
"checksum anyhow 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)" = "7825f6833612eb2414095684fcf6c635becf3ce97fe48cf6421321e93bfbd53c" "checksum anyhow 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)" = "7825f6833612eb2414095684fcf6c635becf3ce97fe48cf6421321e93bfbd53c"
"checksum approx 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "08abcc3b4e9339e33a3d0a5ed15d84a687350c05689d825e0f6655eef9e76a94"
"checksum ar 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "579681b3fecd1e9d6b5ce6969e05f9feb913f296eddaf595be1166a5ca597bc4" "checksum ar 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "579681b3fecd1e9d6b5ce6969e05f9feb913f296eddaf595be1166a5ca597bc4"
"checksum arc-swap 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)" = "bc4662175ead9cd84451d5c35070517777949a2ed84551764129cedb88384841" "checksum arc-swap 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)" = "bc4662175ead9cd84451d5c35070517777949a2ed84551764129cedb88384841"
"checksum arc-swap 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "b585a98a234c46fc563103e9278c9391fde1f4e6850334da895d27edb9580f62" "checksum arc-swap 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "b585a98a234c46fc563103e9278c9391fde1f4e6850334da895d27edb9580f62"
@ -6402,7 +6372,6 @@ dependencies = [
"checksum cc 1.0.49 (registry+https://github.com/rust-lang/crates.io-index)" = "e450b8da92aa6f274e7c6437692f9f2ce6d701fb73bacfcf87897b3f89a4c20e" "checksum cc 1.0.49 (registry+https://github.com/rust-lang/crates.io-index)" = "e450b8da92aa6f274e7c6437692f9f2ce6d701fb73bacfcf87897b3f89a4c20e"
"checksum cexpr 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f4aedb84272dbe89af497cf81375129abda4fc0a9e7c5d317498c15cc30c0d27" "checksum cexpr 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f4aedb84272dbe89af497cf81375129abda4fc0a9e7c5d317498c15cc30c0d27"
"checksum cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "b486ce3ccf7ffd79fdeb678eac06a9e6c09fc88d33836340becb8fffe87c5e33" "checksum cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "b486ce3ccf7ffd79fdeb678eac06a9e6c09fc88d33836340becb8fffe87c5e33"
"checksum cgmath 0.16.1 (registry+https://github.com/rust-lang/crates.io-index)" = "64a4b57c8f4e3a2e9ac07e0f6abc9c24b6fc9e1b54c3478cfb598f3d0023e51c"
"checksum chashmap 2.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ff41a3c2c1e39921b9003de14bf0439c7b63a9039637c291e1a64925d8ddfa45" "checksum chashmap 2.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ff41a3c2c1e39921b9003de14bf0439c7b63a9039637c291e1a64925d8ddfa45"
"checksum chrono 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "80094f509cf8b5ae86a4966a39b3ff66cd7e2a3e594accec3743ff3fabeab5b2" "checksum chrono 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "80094f509cf8b5ae86a4966a39b3ff66cd7e2a3e594accec3743ff3fabeab5b2"
"checksum clang-sys 0.29.3 (registry+https://github.com/rust-lang/crates.io-index)" = "fe6837df1d5cba2397b835c8530f51723267e16abbf83892e9e5af4f0e5dd10a" "checksum clang-sys 0.29.3 (registry+https://github.com/rust-lang/crates.io-index)" = "fe6837df1d5cba2397b835c8530f51723267e16abbf83892e9e5af4f0e5dd10a"
@ -6412,7 +6381,7 @@ dependencies = [
"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" "checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
"checksum codespan 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "004def512a9848b23a68ed110927d102b0e6d9f3dc732e28570879afde051f8c" "checksum codespan 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "004def512a9848b23a68ed110927d102b0e6d9f3dc732e28570879afde051f8c"
"checksum codespan-reporting 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ab081a14ab8f9598ce826890fe896d0addee68c7a58ab49008369ccbb51510a8" "checksum codespan-reporting 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ab081a14ab8f9598ce826890fe896d0addee68c7a58ab49008369ccbb51510a8"
"checksum colored 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6cdb90b60f2927f8d76139c72dbde7e10c3a2bc47c8594c9c7a66529f2687c03" "checksum colored 1.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "f4ffc801dacf156c5854b9df4f425a626539c3a6ef7893cc0c5084a23f0b6c59"
"checksum combine 2.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1645a65a99c7c8d345761f4b75a6ffe5be3b3b27a93ee731fccc5050ba6be97c" "checksum combine 2.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1645a65a99c7c8d345761f4b75a6ffe5be3b3b27a93ee731fccc5050ba6be97c"
"checksum console 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "703dd7516d1a3e6483189c8d9a18a9af84877084630875ff9e922c201f9e0209" "checksum console 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "703dd7516d1a3e6483189c8d9a18a9af84877084630875ff9e922c201f9e0209"
"checksum constant_time_eq 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8ff012e225ce166d4422e0e78419d901719760f62ae2b7969ca6b564d1b54a9e" "checksum constant_time_eq 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8ff012e225ce166d4422e0e78419d901719760f62ae2b7969ca6b564d1b54a9e"
@ -6506,7 +6475,7 @@ dependencies = [
"checksum globset 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "925aa2cac82d8834e2b2a4415b6f6879757fb5c0928fc445ae76461a12eed8f2" "checksum globset 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "925aa2cac82d8834e2b2a4415b6f6879757fb5c0928fc445ae76461a12eed8f2"
"checksum h2 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)" = "a5b34c246847f938a410a03c5458c7fee2274436675e76d8b903c08efc29c462" "checksum h2 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)" = "a5b34c246847f938a410a03c5458c7fee2274436675e76d8b903c08efc29c462"
"checksum h2 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9d5c295d1c0c68e4e42003d75f908f5e16a1edd1cbe0b0d02e4dc2006a384f47" "checksum h2 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9d5c295d1c0c68e4e42003d75f908f5e16a1edd1cbe0b0d02e4dc2006a384f47"
"checksum hash32 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "12d790435639c06a7b798af9e1e331ae245b7ef915b92f70a39b4cf8c00686af" "checksum hash32 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d4041af86e63ac4298ce40e5cca669066e75b6f1aa3390fe2561ffa5e1d9f4cc"
"checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" "checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205"
"checksum hermit-abi 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "307c3c9f937f38e3534b1d6447ecf090cafcc9744e4a6360e8b037b2cf5af120" "checksum hermit-abi 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "307c3c9f937f38e3534b1d6447ecf090cafcc9744e4a6360e8b037b2cf5af120"
"checksum hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "805026a5d0141ffc30abb3be3173848ad46a1b1664fe632428479619a3644d77" "checksum hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "805026a5d0141ffc30abb3be3173848ad46a1b1664fe632428479619a3644d77"
@ -6696,7 +6665,6 @@ dependencies = [
"checksum rental 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)" = "01916ebd9fc2e81978a5dc9542a2fa47f5bb2ca3402e14c7cc42d6e3c5123e1f" "checksum rental 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)" = "01916ebd9fc2e81978a5dc9542a2fa47f5bb2ca3402e14c7cc42d6e3c5123e1f"
"checksum rental-impl 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)" = "82260d54cf2cbe9608df161f7e7c98e81fae702aa13af9e4d5d39dc2ffb25ab6" "checksum rental-impl 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)" = "82260d54cf2cbe9608df161f7e7c98e81fae702aa13af9e4d5d39dc2ffb25ab6"
"checksum reqwest 0.10.4 (registry+https://github.com/rust-lang/crates.io-index)" = "02b81e49ddec5109a9dcfc5f2a317ff53377c915e9ae9d4f2fb50914b85614e2" "checksum reqwest 0.10.4 (registry+https://github.com/rust-lang/crates.io-index)" = "02b81e49ddec5109a9dcfc5f2a317ff53377c915e9ae9d4f2fb50914b85614e2"
"checksum rgb 0.8.13 (registry+https://github.com/rust-lang/crates.io-index)" = "4f089652ca87f5a82a62935ec6172a534066c7b97be003cc8f702ee9a7a59c92"
"checksum ring 0.16.11 (registry+https://github.com/rust-lang/crates.io-index)" = "741ba1704ae21999c00942f9f5944f801e977f54302af346b596287599ad1862" "checksum ring 0.16.11 (registry+https://github.com/rust-lang/crates.io-index)" = "741ba1704ae21999c00942f9f5944f801e977f54302af346b596287599ad1862"
"checksum rocksdb 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)" = "61aa17a99a2413cd71c1106691bf59dad7de0cd5099127f90e9d99c429c40d4a" "checksum rocksdb 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)" = "61aa17a99a2413cd71c1106691bf59dad7de0cd5099127f90e9d99c429c40d4a"
"checksum rpassword 4.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "99371657d3c8e4d816fb6221db98fa408242b0b53bac08f8676a41f8554fe99f" "checksum rpassword 4.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "99371657d3c8e4d816fb6221db98fa408242b0b53bac08f8676a41f8554fe99f"
@ -6888,7 +6856,6 @@ dependencies = [
"checksum winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7168bab6e1daee33b4557efd0e95d5ca70a03706d39fa5f3fe7a236f584b03c9" "checksum winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7168bab6e1daee33b4557efd0e95d5ca70a03706d39fa5f3fe7a236f584b03c9"
"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
"checksum wincolor 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "561ed901ae465d6185fa7864d63fbd5720d0ef718366c9a4dc83cf6170d7e9ba" "checksum wincolor 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "561ed901ae465d6185fa7864d63fbd5720d0ef718366c9a4dc83cf6170d7e9ba"
"checksum winconsole 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ef84b96d10db72dd980056666d7f1e7663ce93d82fa33b63e71c966f4cf5032"
"checksum winreg 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b2986deb581c4fe11b621998a5e53361efe6b48a151178d0cd9eeffa4dc6acc9" "checksum winreg 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b2986deb581c4fe11b621998a5e53361efe6b48a151178d0cd9eeffa4dc6acc9"
"checksum winreg 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69" "checksum winreg 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69"
"checksum ws 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c51a2c47b5798ccc774ffb93ff536aec7c4275d722fd9c740c83cdd1af1f2d94" "checksum ws 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c51a2c47b5798ccc774ffb93ff536aec7c4275d722fd9c740c83cdd1af1f2d94"

View File

@ -1738,11 +1738,12 @@ version = "1.2.0"
dependencies = [ dependencies = [
"bincode 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "bincode 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)", "jemalloc-sys 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"num-derive 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "num-derive 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"solana-logger 1.2.0", "solana-logger 1.2.0",
"solana-runtime 1.2.0",
"solana-sdk 1.2.0", "solana-sdk 1.2.0",
"solana_rbpf 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)", "solana_rbpf 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)",
"thiserror 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", "thiserror 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1824,6 +1825,23 @@ dependencies = [
"solana-sdk-bpf-test 1.2.0", "solana-sdk-bpf-test 1.2.0",
] ]
[[package]]
name = "solana-bpf-rust-invoke"
version = "1.0.0"
dependencies = [
"solana-bpf-rust-invoked 1.0.0",
"solana-sdk 1.2.0",
"solana-sdk-bpf-test 1.2.0",
]
[[package]]
name = "solana-bpf-rust-invoked"
version = "1.0.0"
dependencies = [
"solana-sdk 1.2.0",
"solana-sdk-bpf-test 1.2.0",
]
[[package]] [[package]]
name = "solana-bpf-rust-iter" name = "solana-bpf-rust-iter"
version = "1.2.0" version = "1.2.0"

View File

@ -40,6 +40,8 @@ members = [
"rust/dup_accounts", "rust/dup_accounts",
"rust/error_handling", "rust/error_handling",
"rust/external_spend", "rust/external_spend",
"rust/invoke",
"rust/invoked",
"rust/iter", "rust/iter",
"rust/many_args", "rust/many_args",
"rust/many_args_dep", "rust/many_args_dep",

View File

@ -4,6 +4,14 @@ extern crate test;
use byteorder::{ByteOrder, LittleEndian, WriteBytesExt}; use byteorder::{ByteOrder, LittleEndian, WriteBytesExt};
use solana_rbpf::EbpfVm; use solana_rbpf::EbpfVm;
use solana_sdk::{
account::Account,
entrypoint_native::InvokeContext,
instruction::{CompiledInstruction, InstructionError},
message::Message,
pubkey::Pubkey,
};
use std::{cell::RefCell, rc::Rc};
use std::{env, fs::File, io::Read, mem, path::PathBuf}; use std::{env, fs::File, io::Read, mem, path::PathBuf};
use test::Bencher; use test::Bencher;
@ -69,9 +77,10 @@ fn bench_program_alu(bencher: &mut Bencher) {
.write_u64::<LittleEndian>(ARMSTRONG_LIMIT) .write_u64::<LittleEndian>(ARMSTRONG_LIMIT)
.unwrap(); .unwrap();
inner_iter.write_u64::<LittleEndian>(0).unwrap(); inner_iter.write_u64::<LittleEndian>(0).unwrap();
let mut invoke_context = MockInvokeContext::default();
let elf = load_elf().unwrap(); let elf = load_elf().unwrap();
let (mut vm, _) = solana_bpf_loader_program::create_vm(&elf).unwrap(); let (mut vm, _) = solana_bpf_loader_program::create_vm(&elf, &mut invoke_context).unwrap();
println!("Interpreted:"); println!("Interpreted:");
assert_eq!( assert_eq!(
@ -122,3 +131,21 @@ fn bench_program_alu(bencher: &mut Bencher) {
// println!(" {:?} MIPS", mips); // println!(" {:?} MIPS", mips);
// println!("{{ \"type\": \"bench\", \"name\": \"bench_program_alu_jit_to_native_mips\", \"median\": {:?}, \"deviation\": 0 }}", mips); // println!("{{ \"type\": \"bench\", \"name\": \"bench_program_alu_jit_to_native_mips\", \"median\": {:?}, \"deviation\": 0 }}", mips);
} }
#[derive(Debug, Default)]
pub struct MockInvokeContext {}
impl InvokeContext for MockInvokeContext {
fn push(&mut self, _key: &Pubkey) -> Result<(), InstructionError> {
Ok(())
}
fn pop(&mut self) {}
fn verify_and_update(
&mut self,
_message: &Message,
_instruction: &CompiledInstruction,
_signers: &[Pubkey],
_accounts: &[Rc<RefCell<Account>>],
) -> Result<(), InstructionError> {
Ok(())
}
}

View File

@ -71,6 +71,8 @@ fn main() {
"dup_accounts", "dup_accounts",
"error_handling", "error_handling",
"external_spend", "external_spend",
"invoke",
"invoked",
"iter", "iter",
"many_args", "many_args",
"noop", "noop",

View File

@ -0,0 +1,131 @@
/**
* @brief Example C-based BPF program that prints out the parameters
* passed to it
*/
#include <solana_sdk.h>
#define MINT_INDEX 0
#define ARGUMENT_INDEX 1
#define INVOKED_PROGRAM_INDEX 2
#define INVOKED_ARGUMENT_INDEX 3
#define INVOKED_PROGRAM_DUP_INDEX 4
#define ARGUMENT_DUP_INDEX 5
#define DERIVED_KEY_INDEX 6
#define DERIVED_KEY2_INDEX 7
extern uint64_t entrypoint(const uint8_t *input) {
sol_log("Invoke C program");
SolAccountInfo accounts[8];
SolParameters params = (SolParameters){.ka = accounts};
if (!sol_deserialize(input, &params, SOL_ARRAY_SIZE(accounts))) {
return ERROR_INVALID_ARGUMENT;
}
sol_log("Test data translation");
{
for (int i = 0; i < accounts[ARGUMENT_INDEX].data_len; i++) {
accounts[ARGUMENT_INDEX].data[i] = i;
}
SolAccountMeta arguments[] = {
{accounts[ARGUMENT_INDEX].key, true, true},
{accounts[INVOKED_ARGUMENT_INDEX].key, true, true},
{accounts[INVOKED_PROGRAM_INDEX].key, false, false},
{accounts[INVOKED_PROGRAM_DUP_INDEX].key, false, false}};
uint8_t data[] = {0, 1, 2, 3, 4, 5};
const SolInstruction instruction = {accounts[INVOKED_PROGRAM_INDEX].key,
arguments, 4, data, 6};
sol_assert(SUCCESS == sol_invoke(&instruction, accounts,
SOL_ARRAY_SIZE(accounts)));
}
sol_log("Test return error");
{
SolAccountMeta arguments[] = {{accounts[ARGUMENT_INDEX].key, true, true}};
uint8_t data[] = {1};
const SolInstruction instruction = {accounts[INVOKED_PROGRAM_INDEX].key,
arguments, SOL_ARRAY_SIZE(arguments),
data, SOL_ARRAY_SIZE(data)};
sol_assert(42 == sol_invoke(&instruction, accounts,
SOL_ARRAY_SIZE(accounts)));
}
sol_log("Test derived signers");
{
SolAccountMeta arguments[] = {
{accounts[DERIVED_KEY_INDEX].key, true, true},
{accounts[DERIVED_KEY2_INDEX].key, false, true}};
uint8_t data[] = {2};
const SolInstruction instruction = {accounts[INVOKED_PROGRAM_INDEX].key,
arguments, SOL_ARRAY_SIZE(arguments),
data, SOL_ARRAY_SIZE(data)};
const SolSignerSeed seeds1[] = {{"Lil'", 4}, {"Bits", 4}};
const SolSignerSeed seeds2[] = {{"Gar Ma Nar Nar", 14}};
const SolSignerSeeds signers_seeds[] = {{seeds1, SOL_ARRAY_SIZE(seeds1)},
{seeds2, SOL_ARRAY_SIZE(seeds2)}};
sol_assert(SUCCESS == sol_invoke_signed(
&instruction, accounts, SOL_ARRAY_SIZE(accounts),
signers_seeds, SOL_ARRAY_SIZE(signers_seeds)));
}
sol_log("Test readonly with writable account");
{
SolAccountMeta arguments[] = {
{accounts[INVOKED_ARGUMENT_INDEX].key, true, false}};
uint8_t data[] = {3};
const SolInstruction instruction = {accounts[INVOKED_PROGRAM_INDEX].key,
arguments, SOL_ARRAY_SIZE(arguments),
data, SOL_ARRAY_SIZE(data)};
sol_assert(SUCCESS == sol_invoke(&instruction, accounts,
SOL_ARRAY_SIZE(accounts)));
}
sol_log("Test invoke");
{
sol_assert(accounts[ARGUMENT_INDEX].is_signer);
sol_assert(!accounts[DERIVED_KEY_INDEX].is_signer);
sol_assert(!accounts[DERIVED_KEY2_INDEX].is_signer);
*accounts[ARGUMENT_INDEX].lamports -= 5;
*accounts[INVOKED_ARGUMENT_INDEX].lamports += 5;
SolAccountMeta arguments[] = {
{accounts[INVOKED_ARGUMENT_INDEX].key, true, true},
{accounts[ARGUMENT_INDEX].key, true, true},
{accounts[DERIVED_KEY_INDEX].key, true, true}};
uint8_t data[] = {4};
const SolInstruction instruction = {accounts[INVOKED_PROGRAM_INDEX].key,
arguments, SOL_ARRAY_SIZE(arguments),
data, SOL_ARRAY_SIZE(data)};
const SolSignerSeed seeds[] = {{"Lil'", 4}, {"Bits", 4}};
const SolSignerSeeds signers_seeds[] = {{seeds, SOL_ARRAY_SIZE(seeds)}};
sol_log("Fist invoke");
sol_assert(SUCCESS == sol_invoke_signed(
&instruction, accounts, SOL_ARRAY_SIZE(accounts),
signers_seeds, SOL_ARRAY_SIZE(signers_seeds)));
sol_log("2nd invoke from first program");
sol_assert(SUCCESS == sol_invoke_signed(
&instruction, accounts, SOL_ARRAY_SIZE(accounts),
signers_seeds, SOL_ARRAY_SIZE(signers_seeds)));
sol_assert(*accounts[ARGUMENT_INDEX].lamports == 42 - 5 + 1 + 1);
sol_assert(*accounts[INVOKED_ARGUMENT_INDEX].lamports == 10 + 5 - 1 - 1);
}
sol_log("Verify data values are retained and updated");
for (int i = 0; i < accounts[ARGUMENT_INDEX].data_len; i++) {
sol_assert(accounts[ARGUMENT_INDEX].data[i] == i);
}
for (int i = 0; i < accounts[INVOKED_ARGUMENT_INDEX].data_len; i++) {
sol_assert(accounts[INVOKED_ARGUMENT_INDEX].data[i] == i);
}
return SUCCESS;
}

View File

@ -0,0 +1,126 @@
/**
* @brief Example C-based BPF program that prints out the parameters
* passed to it
*/
#include <solana_sdk.h>
extern uint64_t entrypoint(const uint8_t *input) {
SolAccountInfo accounts[4];
SolParameters params = (SolParameters){.ka = accounts};
if (!sol_deserialize(input, &params, 0)) {
return ERROR_INVALID_ARGUMENT;
}
switch (params.data[0]) {
case (0): {
sol_log("verify data translations");
static const int ARGUMENT_INDEX = 0;
static const int INVOKED_ARGUMENT_INDEX = 1;
static const int INVOKED_PROGRAM_INDEX = 2;
static const int INVOKED_PROGRAM_DUP_INDEX = 3;
sol_assert(sol_deserialize(input, &params, 4));
SolPubkey bpf_loader_id =
(SolPubkey){.x = {2, 168, 246, 145, 78, 136, 161, 107, 189, 35, 149,
133, 95, 100, 4, 217, 180, 244, 86, 183, 130, 27,
176, 20, 87, 73, 66, 140, 0, 0, 0, 0}};
for (int i = 0; i < params.data_len; i++) {
sol_assert(params.data[i] == i);
}
sol_assert(params.ka_num == 4);
sol_assert(*accounts[ARGUMENT_INDEX].lamports == 42);
sol_assert(accounts[ARGUMENT_INDEX].data_len == 100);
sol_assert(accounts[ARGUMENT_INDEX].is_signer);
sol_assert(accounts[ARGUMENT_INDEX].is_writable);
sol_assert(accounts[ARGUMENT_INDEX].rent_epoch == 1);
sol_assert(!accounts[ARGUMENT_INDEX].executable);
for (int i = 0; i < accounts[ARGUMENT_INDEX].data_len; i++) {
sol_assert(accounts[ARGUMENT_INDEX].data[i] == i);
}
sol_assert(SolPubkey_same(accounts[INVOKED_ARGUMENT_INDEX].owner,
accounts[INVOKED_PROGRAM_INDEX].key));
sol_assert(*accounts[INVOKED_ARGUMENT_INDEX].lamports == 10);
sol_assert(accounts[INVOKED_ARGUMENT_INDEX].data_len == 10);
sol_assert(accounts[INVOKED_ARGUMENT_INDEX].is_signer);
sol_assert(accounts[INVOKED_ARGUMENT_INDEX].is_writable);
sol_assert(accounts[INVOKED_ARGUMENT_INDEX].rent_epoch == 1);
sol_assert(!accounts[INVOKED_ARGUMENT_INDEX].executable);
sol_assert(
SolPubkey_same(accounts[INVOKED_PROGRAM_INDEX].key, params.program_id))
sol_assert(SolPubkey_same(accounts[INVOKED_PROGRAM_INDEX].owner,
&bpf_loader_id));
sol_assert(!accounts[INVOKED_PROGRAM_INDEX].is_signer);
sol_assert(!accounts[INVOKED_PROGRAM_INDEX].is_writable);
sol_assert(accounts[INVOKED_PROGRAM_INDEX].rent_epoch == 1);
sol_assert(accounts[INVOKED_PROGRAM_INDEX].executable);
sol_assert(SolPubkey_same(accounts[INVOKED_PROGRAM_INDEX].key,
accounts[INVOKED_PROGRAM_DUP_INDEX].key));
sol_assert(SolPubkey_same(accounts[INVOKED_PROGRAM_INDEX].owner,
accounts[INVOKED_PROGRAM_DUP_INDEX].owner));
sol_assert(*accounts[INVOKED_PROGRAM_INDEX].lamports ==
*accounts[INVOKED_PROGRAM_DUP_INDEX].lamports);
sol_assert(accounts[INVOKED_PROGRAM_INDEX].is_signer ==
accounts[INVOKED_PROGRAM_DUP_INDEX].is_signer);
sol_assert(accounts[INVOKED_PROGRAM_INDEX].is_writable ==
accounts[INVOKED_PROGRAM_DUP_INDEX].is_writable);
sol_assert(accounts[INVOKED_PROGRAM_INDEX].rent_epoch ==
accounts[INVOKED_PROGRAM_DUP_INDEX].rent_epoch);
sol_assert(accounts[INVOKED_PROGRAM_INDEX].executable ==
accounts[INVOKED_PROGRAM_DUP_INDEX].executable);
break;
}
case (1): {
sol_log("reutrn error");
return 42;
}
case (2): {
sol_log("verify derived signers");
static const int DERIVED_KEY_INDEX = 0;
static const int DERIVED_KEY2_INDEX = 1;
sol_assert(sol_deserialize(input, &params, 2));
sol_assert(accounts[DERIVED_KEY_INDEX].is_signer);
sol_assert(accounts[DERIVED_KEY2_INDEX].is_signer);
break;
}
case (3): {
sol_log("verify writable");
static const int ARGUMENT_INDEX = 0;
sol_assert(sol_deserialize(input, &params, 1));
sol_assert(accounts[ARGUMENT_INDEX].is_writable);
break;
}
case (4): {
sol_log("invoke");
static const int INVOKED_ARGUMENT_INDEX = 0;
static const int ARGUMENT_INDEX = 1;
static const int DERIVED_KEY_INDEX = 2;
sol_assert(sol_deserialize(input, &params, 3));
sol_assert(accounts[INVOKED_ARGUMENT_INDEX].is_signer);
sol_assert(accounts[ARGUMENT_INDEX].is_signer);
sol_assert(accounts[DERIVED_KEY_INDEX].is_signer);
*accounts[INVOKED_ARGUMENT_INDEX].lamports -= 1;
*accounts[ARGUMENT_INDEX].lamports += 1;
sol_log("Last invoke");
for (int i = 0; i < accounts[INVOKED_ARGUMENT_INDEX].data_len; i++) {
accounts[INVOKED_ARGUMENT_INDEX].data[i] = i;
}
break;
}
default:
return ERROR_INVALID_INSTRUCTION_DATA;
}
return SUCCESS;
}

View File

@ -0,0 +1,27 @@
# Note: This crate must be built using do.sh
[package]
name = "solana-bpf-rust-invoke"
version = "1.0.0"
description = "Solana BPF test program written in Rust"
authors = ["Solana Maintainers <maintainers@solana.com>"]
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
edition = "2018"
[dependencies]
solana-sdk = { path = "../../../../sdk/", version = "1.0.0", default-features = false }
solana-bpf-rust-invoked = { path = "../invoked"}
[dev_dependencies]
solana-sdk-bpf-test = { path = "../../../../sdk/bpf/rust/test", version = "1.0.0" }
[features]
program = ["solana-sdk/program"]
default = ["program"]
[lib]
name = "solana_bpf_rust_invoke"
crate-type = ["cdylib"]

View File

@ -0,0 +1,2 @@
[target.bpfel-unknown-unknown.dependencies.std]
features = []

View File

@ -0,0 +1,145 @@
//! @brief Example Rust-based BPF program that issues a cross-program-invocation
#![allow(unreachable_code)]
extern crate solana_sdk;
use solana_bpf_rust_invoked::instruction::create_instruction;
use solana_sdk::{
account_info::AccountInfo,
entrypoint,
entrypoint::ProgramResult,
info,
program::{invoke, invoke_signed},
program_error::ProgramError,
pubkey::Pubkey,
};
// const MINT_INDEX: usize = 0;
const ARGUMENT_INDEX: usize = 1;
const INVOKED_PROGRAM_INDEX: usize = 2;
const INVOKED_ARGUMENT_INDEX: usize = 3;
const INVOKED_PROGRAM_DUP_INDEX: usize = 4;
// const ARGUMENT_DUP_INDEX: usize = 5;
const DERIVED_KEY_INDEX: usize = 6;
const DERIVED_KEY2_INDEX: usize = 7;
entrypoint!(process_instruction);
fn process_instruction(
_program_id: &Pubkey,
accounts: &[AccountInfo],
_instruction_data: &[u8],
) -> ProgramResult {
info!("invoke Rust program");
info!("Test data translation");
{
{
let mut data = accounts[ARGUMENT_INDEX].try_borrow_mut_data()?;
for i in 0..100 {
data[i as usize] = i;
}
}
let instruction = create_instruction(
*accounts[INVOKED_PROGRAM_INDEX].key,
&[
(accounts[ARGUMENT_INDEX].key, true, true),
(accounts[INVOKED_ARGUMENT_INDEX].key, true, true),
(accounts[INVOKED_PROGRAM_INDEX].key, false, false),
(accounts[INVOKED_PROGRAM_DUP_INDEX].key, false, false),
],
vec![0, 1, 2, 3, 4, 5],
);
invoke(&instruction, accounts)?;
}
info!("Test return error");
{
let instruction = create_instruction(
*accounts[INVOKED_PROGRAM_INDEX].key,
&[(accounts[ARGUMENT_INDEX].key, true, true)],
vec![1],
);
assert_eq!(
invoke(&instruction, accounts),
Err(ProgramError::Custom(42))
);
}
info!("Test derived signers");
{
assert!(!accounts[DERIVED_KEY_INDEX].is_signer);
assert!(!accounts[DERIVED_KEY2_INDEX].is_signer);
let invoked_instruction = create_instruction(
*accounts[INVOKED_PROGRAM_INDEX].key,
&[
(accounts[DERIVED_KEY_INDEX].key, true, true),
(accounts[DERIVED_KEY2_INDEX].key, false, true),
],
vec![2],
);
invoke_signed(
&invoked_instruction,
accounts,
&[&["Lil'", "Bits"], &["Gar Ma Nar Nar"]],
)?;
}
info!("Test readonly with writable account");
{
let invoked_instruction = create_instruction(
*accounts[INVOKED_PROGRAM_INDEX].key,
&[(accounts[ARGUMENT_INDEX].key, false, true)],
vec![3],
);
invoke(&invoked_instruction, accounts)?;
}
info!("Test nested invoke");
{
assert!(accounts[ARGUMENT_INDEX].is_signer);
assert!(!accounts[DERIVED_KEY_INDEX].is_signer);
assert!(!accounts[DERIVED_KEY2_INDEX].is_signer);
**accounts[ARGUMENT_INDEX].lamports.borrow_mut() -= 5;
**accounts[INVOKED_ARGUMENT_INDEX].lamports.borrow_mut() += 5;
info!("Fist invoke");
let instruction = create_instruction(
*accounts[INVOKED_PROGRAM_INDEX].key,
&[
(accounts[ARGUMENT_INDEX].key, true, true),
(accounts[INVOKED_ARGUMENT_INDEX].key, true, true),
(accounts[DERIVED_KEY_INDEX].key, true, false),
(accounts[INVOKED_PROGRAM_DUP_INDEX].key, false, false),
(accounts[INVOKED_PROGRAM_DUP_INDEX].key, false, false),
],
vec![4],
);
invoke(&instruction, accounts)?;
info!("2nd invoke from first program");
invoke(&instruction, accounts)?;
assert_eq!(accounts[ARGUMENT_INDEX].lamports(), 42 - 5 + 1 + 1 + 1 + 1);
assert_eq!(
accounts[INVOKED_ARGUMENT_INDEX].lamports(),
10 + 5 - 1 - 1 - 1 - 1
);
}
info!("Verify data values are retained and updated");
{
let data = accounts[ARGUMENT_INDEX].try_borrow_data()?;
for i in 0..100 {
assert_eq!(data[i as usize], i);
}
let data = accounts[INVOKED_ARGUMENT_INDEX].try_borrow_data()?;
for i in 0..10 {
assert_eq!(data[i as usize], i);
}
}
Ok(())
}

View File

@ -0,0 +1,26 @@
# Note: This crate must be built using do.sh
[package]
name = "solana-bpf-rust-invoked"
version = "1.0.0"
description = "Solana BPF test program written in Rust"
authors = ["Solana Maintainers <maintainers@solana.com>"]
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
edition = "2018"
[dependencies]
solana-sdk = { path = "../../../../sdk/", version = "1.0.0", default-features = false }
[dev_dependencies]
solana-sdk-bpf-test = { path = "../../../../sdk/bpf/rust/test", version = "1.0.0" }
[features]
program = ["solana-sdk/program"]
default = ["program"]
[lib]
name = "solana_bpf_rust_invoked"
crate-type = ["lib", "cdylib"]

View File

@ -0,0 +1,2 @@
[target.bpfel-unknown-unknown.dependencies.std]
features = []

View File

@ -0,0 +1,28 @@
//! @brief Example Rust-based BPF program that issues a cross-program-invocation
use solana_sdk::{
instruction::{AccountMeta, Instruction},
pubkey::Pubkey,
};
pub fn create_instruction(
program_id: Pubkey,
arguments: &[(&Pubkey, bool, bool)],
data: Vec<u8>,
) -> Instruction {
let accounts = arguments
.iter()
.map(|(key, is_writable, is_signer)| {
if *is_writable {
AccountMeta::new(**key, *is_signer)
} else {
AccountMeta::new_readonly(**key, *is_signer)
}
})
.collect();
Instruction {
program_id,
accounts,
data,
}
}

View File

@ -0,0 +1,160 @@
//! @brief Example Rust-based BPF program that issues a cross-program-invocation
#![allow(unreachable_code)]
pub mod instruction;
extern crate solana_sdk;
use crate::instruction::create_instruction;
use solana_sdk::{
account_info::AccountInfo, bpf_loader, entrypoint, entrypoint::ProgramResult, info,
program::invoke_signed, program_error::ProgramError, pubkey::Pubkey,
};
entrypoint!(process_instruction);
#[allow(clippy::cognitive_complexity)]
fn process_instruction(
program_id: &Pubkey,
accounts: &[AccountInfo],
instruction_data: &[u8],
) -> ProgramResult {
info!("Invoked program");
match instruction_data[0] {
0 => {
info!("verify data translations");
const ARGUMENT_INDEX: usize = 0;
const INVOKED_ARGUMENT_INDEX: usize = 1;
const INVOKED_PROGRAM_INDEX: usize = 2;
const INVOKED_PROGRAM_DUP_INDEX: usize = 3;
assert_eq!(instruction_data, &[0, 1, 2, 3, 4, 5]);
assert_eq!(accounts.len(), 4);
assert_eq!(accounts[ARGUMENT_INDEX].lamports(), 42);
assert_eq!(accounts[ARGUMENT_INDEX].data_len(), 100);
assert!(accounts[ARGUMENT_INDEX].is_signer);
assert!(accounts[ARGUMENT_INDEX].is_writable);
assert_eq!(accounts[ARGUMENT_INDEX].rent_epoch, 1);
assert!(!accounts[ARGUMENT_INDEX].executable);
{
let data = accounts[ARGUMENT_INDEX].try_borrow_data()?;
for i in 0..100 {
assert_eq!(data[i as usize], i);
}
}
assert_eq!(
accounts[INVOKED_ARGUMENT_INDEX].owner,
accounts[INVOKED_PROGRAM_INDEX].key
);
assert_eq!(accounts[INVOKED_ARGUMENT_INDEX].lamports(), 10);
assert_eq!(accounts[INVOKED_ARGUMENT_INDEX].data_len(), 10);
assert!(accounts[INVOKED_ARGUMENT_INDEX].is_signer);
assert!(accounts[INVOKED_ARGUMENT_INDEX].is_writable);
assert_eq!(accounts[INVOKED_ARGUMENT_INDEX].rent_epoch, 1);
assert!(!accounts[INVOKED_ARGUMENT_INDEX].executable);
assert_eq!(accounts[INVOKED_PROGRAM_INDEX].key, program_id);
assert_eq!(accounts[INVOKED_PROGRAM_INDEX].owner, &bpf_loader::id());
assert!(!accounts[INVOKED_PROGRAM_INDEX].is_signer);
assert!(!accounts[INVOKED_PROGRAM_INDEX].is_writable);
assert_eq!(accounts[INVOKED_PROGRAM_INDEX].rent_epoch, 1);
assert!(accounts[INVOKED_PROGRAM_INDEX].executable);
assert_eq!(
accounts[INVOKED_PROGRAM_INDEX].key,
accounts[INVOKED_PROGRAM_DUP_INDEX].key
);
assert_eq!(
accounts[INVOKED_PROGRAM_INDEX].owner,
accounts[INVOKED_PROGRAM_DUP_INDEX].owner
);
assert_eq!(
accounts[INVOKED_PROGRAM_INDEX].lamports,
accounts[INVOKED_PROGRAM_DUP_INDEX].lamports
);
assert_eq!(
accounts[INVOKED_PROGRAM_INDEX].is_signer,
accounts[INVOKED_PROGRAM_DUP_INDEX].is_signer
);
assert_eq!(
accounts[INVOKED_PROGRAM_INDEX].is_writable,
accounts[INVOKED_PROGRAM_DUP_INDEX].is_writable
);
assert_eq!(
accounts[INVOKED_PROGRAM_INDEX].rent_epoch,
accounts[INVOKED_PROGRAM_DUP_INDEX].rent_epoch
);
assert_eq!(
accounts[INVOKED_PROGRAM_INDEX].executable,
accounts[INVOKED_PROGRAM_DUP_INDEX].executable
);
{
let data = accounts[INVOKED_PROGRAM_INDEX].try_borrow_data()?;
assert!(accounts[INVOKED_PROGRAM_DUP_INDEX]
.try_borrow_mut_data()
.is_err());
info!(data[0], 0, 0, 0, 0);
}
}
1 => {
info!("return error");
return Err(ProgramError::Custom(42));
}
2 => {
info!("verify derived signers");
const DERIVED_KEY_INDEX: usize = 0;
const DERIVED_KEY2_INDEX: usize = 1;
assert!(accounts[DERIVED_KEY_INDEX].is_signer);
assert!(accounts[DERIVED_KEY2_INDEX].is_signer);
}
3 => {
info!("verify writable");
const ARGUMENT_INDEX: usize = 0;
assert!(!accounts[ARGUMENT_INDEX].is_writable);
}
4 => {
info!("nested invoke");
const ARGUMENT_INDEX: usize = 0;
const INVOKED_ARGUMENT_INDEX: usize = 1;
const DERIVED_KEY_INDEX: usize = 2;
const INVOKED_PROGRAM_INDEX: usize = 3;
assert!(accounts[INVOKED_ARGUMENT_INDEX].is_signer);
**accounts[INVOKED_ARGUMENT_INDEX].lamports.borrow_mut() -= 1;
**accounts[ARGUMENT_INDEX].lamports.borrow_mut() += 1;
if accounts.len() > 3 {
info!("Invoke again");
let invoked_instruction = create_instruction(
*accounts[INVOKED_PROGRAM_INDEX].key,
&[
(accounts[ARGUMENT_INDEX].key, true, true),
(accounts[INVOKED_ARGUMENT_INDEX].key, true, true),
(accounts[DERIVED_KEY_INDEX].key, true, true),
],
vec![4],
);
invoke_signed(&invoked_instruction, accounts, &[&["Lil'", "Bits"]])?;
} else {
info!("Last invoked");
assert!(accounts[DERIVED_KEY_INDEX].is_signer);
{
let mut data = accounts[INVOKED_ARGUMENT_INDEX].try_borrow_mut_data()?;
for i in 0..10 {
data[i as usize] = i;
}
}
}
}
_ => panic!(),
}
Ok(())
}

View File

@ -13,6 +13,7 @@ mod bpf {
client::SyncClient, client::SyncClient,
clock::DEFAULT_SLOTS_PER_EPOCH, clock::DEFAULT_SLOTS_PER_EPOCH,
instruction::{AccountMeta, Instruction, InstructionError}, instruction::{AccountMeta, Instruction, InstructionError},
message::Message,
pubkey::Pubkey, pubkey::Pubkey,
signature::Keypair, signature::Keypair,
signature::Signer, signature::Signer,
@ -303,4 +304,71 @@ mod bpf {
); );
} }
} }
#[test]
fn test_program_bpf_invoke() {
solana_logger::setup();
let mut programs = Vec::new();
#[cfg(feature = "bpf_c")]
{
programs.extend_from_slice(&[("invoke", "invoked")]);
}
#[cfg(feature = "bpf_rust")]
{
programs.extend_from_slice(&[("solana_bpf_rust_invoke", "solana_bpf_rust_invoked")]);
}
for program in programs.iter() {
println!("Test program: {:?}", program);
let GenesisConfigInfo {
mut genesis_config,
mint_keypair,
..
} = create_genesis_config(50);
genesis_config
.native_instruction_processors
.push(solana_bpf_loader_program!());
let bank = Arc::new(Bank::new(&genesis_config));
let bank_client = BankClient::new_shared(&bank);
let program_id = load_bpf_program(&bank_client, &mint_keypair, program.0);
let invoked_program_id = load_bpf_program(&bank_client, &mint_keypair, program.1);
let account = Account::new(42, 100, &program_id);
let argument_keypair = Keypair::new();
bank.store_account(&argument_keypair.pubkey(), &account);
let account = Account::new(10, 10, &invoked_program_id);
let invoked_argument_keypair = Keypair::new();
bank.store_account(&invoked_argument_keypair.pubkey(), &account);
let derived_key =
Pubkey::create_program_address(&["Lil'", "Bits"], &invoked_program_id).unwrap();
let derived_key2 =
Pubkey::create_program_address(&["Gar Ma Nar Nar"], &invoked_program_id).unwrap();
let account_metas = vec![
AccountMeta::new(mint_keypair.pubkey(), true),
AccountMeta::new(argument_keypair.pubkey(), true),
AccountMeta::new_readonly(invoked_program_id, false),
AccountMeta::new(invoked_argument_keypair.pubkey(), true),
AccountMeta::new_readonly(invoked_program_id, false),
AccountMeta::new(argument_keypair.pubkey(), true),
AccountMeta::new(derived_key, false),
AccountMeta::new_readonly(derived_key2, false),
];
let instruction = Instruction::new(program_id, &1u8, account_metas);
let message = Message::new(&[instruction]);
assert!(bank_client
.send_message(
&[&mint_keypair, &argument_keypair, &invoked_argument_keypair],
message,
)
.is_ok());
}
}
} }

View File

@ -11,11 +11,12 @@ edition = "2018"
[dependencies] [dependencies]
bincode = "1.2.1" bincode = "1.2.1"
byteorder = "1.3.4" byteorder = "1.3.4"
libc = "0.2.69" jemalloc-sys = { version = "0.3.2", features = ["disable_initial_exec_tls"] }
log = "0.4.8" log = "0.4.8"
num-derive = { version = "0.3" } num-derive = { version = "0.3" }
num-traits = { version = "0.2" } num-traits = { version = "0.2" }
solana-logger = { path = "../../logger", version = "1.2.0" } solana-logger = { path = "../../logger", version = "1.2.0" }
solana-runtime = { path = "../../runtime", version = "1.2.0" }
solana-sdk = { path = "../../sdk", version = "1.2.0" } solana-sdk = { path = "../../sdk", version = "1.2.0" }
solana_rbpf = "=0.1.25" solana_rbpf = "=0.1.25"
thiserror = "1.0" thiserror = "1.0"

View File

@ -6,10 +6,25 @@ use solana_rbpf::{
memory_region::{translate_addr, MemoryRegion}, memory_region::{translate_addr, MemoryRegion},
EbpfVm, EbpfVm,
}; };
use solana_sdk::instruction::InstructionError; use solana_runtime::message_processor::MessageProcessor;
use solana_sdk::{
account::Account,
account_info::AccountInfo,
bpf_loader,
entrypoint::SUCCESS,
entrypoint_native::InvokeContext,
hash::Hash,
instruction::{AccountMeta, Instruction, InstructionError},
message::Message,
program_error::ProgramError,
pubkey::{Pubkey, PubkeyError},
};
use std::{ use std::{
alloc::Layout, alloc::Layout,
cell::{RefCell, RefMut},
convert::TryFrom,
mem::{align_of, size_of}, mem::{align_of, size_of},
rc::Rc,
slice::from_raw_parts_mut, slice::from_raw_parts_mut,
str::{from_utf8, Utf8Error}, str::{from_utf8, Utf8Error},
}; };
@ -24,6 +39,14 @@ pub enum HelperError {
Abort, Abort,
#[error("BPF program Panicked at {0}, {1}:{2}")] #[error("BPF program Panicked at {0}, {1}:{2}")]
Panic(String, u64, u64), Panic(String, u64, u64),
#[error("cannot borrow invoke context")]
InvokeContextBorrowFailed,
#[error("malformed signer seed: {0}: {1:?}")]
MalformedSignerSeed(Utf8Error, Vec<u8>),
#[error("Could not create program address with signer seeds: {0}")]
BadSeeds(PubkeyError),
#[error("Program id is not supported by cross-program invocations")]
ProgramNotSupported,
#[error("{0}")] #[error("{0}")]
InstructionError(InstructionError), InstructionError(InstructionError),
} }
@ -47,6 +70,7 @@ const DEFAULT_HEAP_SIZE: usize = 32 * 1024;
pub fn register_helpers<'a>( pub fn register_helpers<'a>(
vm: &mut EbpfVm<'a, BPFError>, vm: &mut EbpfVm<'a, BPFError>,
invoke_context: &'a mut dyn InvokeContext,
) -> Result<MemoryRegion, EbpfError<BPFError>> { ) -> Result<MemoryRegion, EbpfError<BPFError>> {
vm.register_helper_ex("abort", helper_abort)?; vm.register_helper_ex("abort", helper_abort)?;
vm.register_helper_ex("sol_panic", helper_sol_panic)?; vm.register_helper_ex("sol_panic", helper_sol_panic)?;
@ -56,6 +80,20 @@ pub fn register_helpers<'a>(
vm.register_helper_ex("sol_log_64", helper_sol_log_u64)?; vm.register_helper_ex("sol_log_64", helper_sol_log_u64)?;
vm.register_helper_ex("sol_log_64_", helper_sol_log_u64)?; vm.register_helper_ex("sol_log_64_", helper_sol_log_u64)?;
let invoke_context = Rc::new(RefCell::new(invoke_context));
vm.register_helper_with_context_ex(
"sol_invoke_signed_rust",
Box::new(HelperProcessInstructionRust {
invoke_context: invoke_context.clone(),
}),
)?;
vm.register_helper_with_context_ex(
"sol_invoke_signed_c",
Box::new(HelperProcessSolInstructionC {
invoke_context: invoke_context.clone(),
}),
)?;
let heap = vec![0_u8; DEFAULT_HEAP_SIZE]; let heap = vec![0_u8; DEFAULT_HEAP_SIZE];
let heap_region = MemoryRegion::new_from_slice(&heap, MM_HEAP_START); let heap_region = MemoryRegion::new_from_slice(&heap, MM_HEAP_START);
vm.register_helper_with_context_ex( vm.register_helper_with_context_ex(
@ -255,13 +293,460 @@ impl HelperObject<BPFError> for HelperSolAllocFree {
} }
} }
// Cross-program invocation helpers
pub type TranslatedAccounts<'a> = (Vec<Rc<RefCell<Account>>>, Vec<(&'a mut u64, &'a mut [u8])>);
/// Implemented by language specific data structure translators
trait HelperProcessInstruction<'a> {
fn get_context_mut(&self) -> Result<RefMut<&'a mut dyn InvokeContext>, EbpfError<BPFError>>;
fn translate_instruction(
&self,
addr: u64,
ro_regions: &[MemoryRegion],
) -> Result<Instruction, EbpfError<BPFError>>;
fn translate_accounts(
&self,
message: &Message,
account_infos_addr: u64,
account_infos_len: usize,
ro_regions: &[MemoryRegion],
rw_regions: &[MemoryRegion],
) -> Result<TranslatedAccounts<'a>, EbpfError<BPFError>>;
fn translate_signers(
&self,
program_id: &Pubkey,
signers_seeds_addr: u64,
signers_seeds_len: usize,
ro_regions: &[MemoryRegion],
) -> Result<Vec<Pubkey>, EbpfError<BPFError>>;
}
/// Cross-program invocation called from Rust
pub struct HelperProcessInstructionRust<'a> {
invoke_context: Rc<RefCell<&'a mut dyn InvokeContext>>,
}
impl<'a> HelperProcessInstruction<'a> for HelperProcessInstructionRust<'a> {
fn get_context_mut(&self) -> Result<RefMut<&'a mut dyn InvokeContext>, EbpfError<BPFError>> {
self.invoke_context
.try_borrow_mut()
.map_err(|_| HelperError::InvokeContextBorrowFailed.into())
}
fn translate_instruction(
&self,
addr: u64,
ro_regions: &[MemoryRegion],
) -> Result<Instruction, EbpfError<BPFError>> {
let ix = translate_type!(Instruction, addr, ro_regions)?;
let accounts = translate_slice!(
AccountMeta,
ix.accounts.as_ptr(),
ix.accounts.len(),
ro_regions
)?
.to_vec();
let data = translate_slice!(u8, ix.data.as_ptr(), ix.data.len(), ro_regions)?.to_vec();
Ok(Instruction {
program_id: ix.program_id,
accounts,
data,
})
}
fn translate_accounts(
&self,
message: &Message,
account_infos_addr: u64,
account_infos_len: usize,
ro_regions: &[MemoryRegion],
rw_regions: &[MemoryRegion],
) -> Result<TranslatedAccounts<'a>, EbpfError<BPFError>> {
let account_infos = if account_infos_len > 0 {
translate_slice!(
AccountInfo,
account_infos_addr,
account_infos_len,
ro_regions
)?
} else {
&[]
};
let mut accounts = Vec::with_capacity(message.account_keys.len());
let mut refs = Vec::with_capacity(message.account_keys.len());
'root: for account_key in message.account_keys.iter() {
for account_info in account_infos.iter() {
let key = translate_type!(Pubkey, account_info.key as *const _, ro_regions)?;
if account_key == key {
let lamports_ref = {
// Double translate lamports out of RefCell
let ptr = translate_type!(u64, account_info.lamports.as_ptr(), ro_regions)?;
translate_type_mut!(u64, *(ptr as *const u64), rw_regions)?
};
let data = {
// Double translate data out of RefCell
let data = *translate_type!(&[u8], account_info.data.as_ptr(), ro_regions)?;
translate_slice_mut!(u8, data.as_ptr(), data.len(), rw_regions)?
};
let owner =
translate_type!(Pubkey, account_info.owner as *const _, ro_regions)?;
accounts.push(Rc::new(RefCell::new(Account {
lamports: *lamports_ref,
data: data.to_vec(),
executable: account_info.executable,
owner: *owner,
rent_epoch: account_info.rent_epoch,
hash: Hash::default(),
})));
refs.push((lamports_ref, data));
continue 'root;
}
}
return Err(HelperError::InstructionError(InstructionError::MissingAccount).into());
}
Ok((accounts, refs))
}
fn translate_signers(
&self,
program_id: &Pubkey,
signers_seeds_addr: u64,
signers_seeds_len: usize,
ro_regions: &[MemoryRegion],
) -> Result<Vec<Pubkey>, EbpfError<BPFError>> {
let mut signers = Vec::new();
if signers_seeds_len > 0 {
let signers_seeds =
translate_slice!(&[&str], signers_seeds_addr, signers_seeds_len, ro_regions)?;
for signer_seeds in signers_seeds.iter() {
let untranslated_seeds =
translate_slice!(&str, signer_seeds.as_ptr(), signer_seeds.len(), ro_regions)?;
let seeds = untranslated_seeds
.iter()
.map(|untranslated_seed| {
let seed_bytes = translate_slice!(
u8,
untranslated_seed.as_ptr(),
untranslated_seed.len(),
ro_regions
)?;
from_utf8(seed_bytes).map_err(|err| {
HelperError::MalformedSignerSeed(err, seed_bytes.to_vec()).into()
})
})
.collect::<Result<Vec<_>, EbpfError<BPFError>>>()?;
let signer = Pubkey::create_program_address(&seeds, program_id)
.map_err(HelperError::BadSeeds)?;
signers.push(signer);
}
Ok(signers)
} else {
Ok(vec![])
}
}
}
impl<'a> HelperObject<BPFError> for HelperProcessInstructionRust<'a> {
fn call(
&mut self,
instruction_addr: u64,
account_infos_addr: u64,
account_infos_len: u64,
signers_seeds_addr: u64,
signers_seeds_len: u64,
ro_regions: &[MemoryRegion],
rw_regions: &[MemoryRegion],
) -> Result<u64, EbpfError<BPFError>> {
call(
self,
instruction_addr,
account_infos_addr,
account_infos_len,
signers_seeds_addr,
signers_seeds_len,
ro_regions,
rw_regions,
)
}
}
/// Rust representation of C's SolInstruction
#[derive(Debug)]
struct SolInstruction {
program_id_addr: u64,
accounts_addr: u64,
accounts_len: usize,
data_addr: u64,
data_len: usize,
}
/// Rust representation of C's SolAccountMeta
#[derive(Debug)]
struct SolAccountMeta {
pubkey_addr: u64,
is_writable: bool,
is_signer: bool,
}
/// Rust representation of C's SolAccountInfo
#[derive(Debug)]
struct SolAccountInfo {
key_addr: u64,
lamports_addr: u64,
data_len: usize,
data_addr: u64,
owner_addr: u64,
rent_epoch: u64,
is_signer: bool,
is_writable: bool,
executable: bool,
}
/// Rust representation of C's SolSignerSeed
#[derive(Debug)]
struct SolSignerSeedC {
addr: u64,
len: u64,
}
/// Rust representation of C's SolSignerSeeds
#[derive(Debug)]
struct SolSignerSeedsC {
addr: u64,
len: u64,
}
/// Cross-program invocation called from C
pub struct HelperProcessSolInstructionC<'a> {
invoke_context: Rc<RefCell<&'a mut dyn InvokeContext>>,
}
impl<'a> HelperProcessInstruction<'a> for HelperProcessSolInstructionC<'a> {
fn get_context_mut(&self) -> Result<RefMut<&'a mut dyn InvokeContext>, EbpfError<BPFError>> {
self.invoke_context
.try_borrow_mut()
.map_err(|_| HelperError::InvokeContextBorrowFailed.into())
}
fn translate_instruction(
&self,
addr: u64,
ro_regions: &[MemoryRegion],
) -> Result<Instruction, EbpfError<BPFError>> {
let ix_c = translate_type!(SolInstruction, addr, ro_regions)?;
let program_id = translate_type!(Pubkey, ix_c.program_id_addr, ro_regions)?;
let meta_cs = translate_slice!(
SolAccountMeta,
ix_c.accounts_addr,
ix_c.accounts_len,
ro_regions
)?;
let data = translate_slice!(u8, ix_c.data_addr, ix_c.data_len, ro_regions)?.to_vec();
let accounts = meta_cs
.iter()
.map(|meta_c| {
let pubkey = translate_type!(Pubkey, meta_c.pubkey_addr, ro_regions)?;
Ok(AccountMeta {
pubkey: *pubkey,
is_signer: meta_c.is_signer,
is_writable: meta_c.is_writable,
})
})
.collect::<Result<Vec<AccountMeta>, EbpfError<BPFError>>>()?;
Ok(Instruction {
program_id: *program_id,
accounts,
data,
})
}
fn translate_accounts(
&self,
message: &Message,
account_infos_addr: u64,
account_infos_len: usize,
ro_regions: &[MemoryRegion],
rw_regions: &[MemoryRegion],
) -> Result<TranslatedAccounts<'a>, EbpfError<BPFError>> {
let account_infos = translate_slice!(
SolAccountInfo,
account_infos_addr,
account_infos_len,
ro_regions
)?;
let mut accounts = Vec::with_capacity(message.account_keys.len());
let mut refs = Vec::with_capacity(message.account_keys.len());
'root: for account_key in message.account_keys.iter() {
for account_info in account_infos.iter() {
let key = translate_type!(Pubkey, account_info.key_addr, ro_regions)?;
if account_key == key {
let lamports_ref =
translate_type_mut!(u64, account_info.lamports_addr, rw_regions)?;
let data = translate_slice_mut!(
u8,
account_info.data_addr,
account_info.data_len,
rw_regions
)?;
let owner = translate_type!(Pubkey, account_info.owner_addr, ro_regions)?;
accounts.push(Rc::new(RefCell::new(Account {
lamports: *lamports_ref,
data: data.to_vec(),
executable: account_info.executable,
owner: *owner,
rent_epoch: account_info.rent_epoch,
hash: Hash::default(),
})));
refs.push((lamports_ref, data));
continue 'root;
}
}
return Err(HelperError::InstructionError(InstructionError::MissingAccount).into());
}
Ok((accounts, refs))
}
fn translate_signers(
&self,
program_id: &Pubkey,
signers_seeds_addr: u64,
signers_seeds_len: usize,
ro_regions: &[MemoryRegion],
) -> Result<Vec<Pubkey>, EbpfError<BPFError>> {
if signers_seeds_len > 0 {
let signers_seeds = translate_slice!(
SolSignerSeedC,
signers_seeds_addr,
signers_seeds_len,
ro_regions
)?;
Ok(signers_seeds
.iter()
.map(|signer_seeds| {
let seeds = translate_slice!(
SolSignerSeedC,
signer_seeds.addr,
signer_seeds.len,
ro_regions
)?;
let seed_strs = seeds
.iter()
.map(|seed| {
let seed_bytes = translate_slice!(u8, seed.addr, seed.len, ro_regions)?;
std::str::from_utf8(seed_bytes).map_err(|err| {
HelperError::MalformedSignerSeed(err, seed_bytes.to_vec()).into()
})
})
.collect::<Result<Vec<_>, EbpfError<BPFError>>>()?;
Pubkey::create_program_address(&seed_strs, program_id)
.map_err(|err| HelperError::BadSeeds(err).into())
})
.collect::<Result<Vec<_>, EbpfError<BPFError>>>()?)
} else {
Ok(vec![])
}
}
}
impl<'a> HelperObject<BPFError> for HelperProcessSolInstructionC<'a> {
fn call(
&mut self,
instruction_addr: u64,
account_infos_addr: u64,
account_infos_len: u64,
signers_seeds_addr: u64,
signers_seeds_len: u64,
ro_regions: &[MemoryRegion],
rw_regions: &[MemoryRegion],
) -> Result<u64, EbpfError<BPFError>> {
call(
self,
instruction_addr,
account_infos_addr,
account_infos_len,
signers_seeds_addr,
signers_seeds_len,
ro_regions,
rw_regions,
)
}
}
/// Call process instruction, common to both Rust and C
fn call<'a>(
helper: &mut dyn HelperProcessInstruction<'a>,
instruction_addr: u64,
account_infos_addr: u64,
account_infos_len: u64,
signers_seeds_addr: u64,
signers_seeds_len: u64,
ro_regions: &[MemoryRegion],
rw_regions: &[MemoryRegion],
) -> Result<u64, EbpfError<BPFError>> {
let mut invoke_context = helper.get_context_mut()?;
// Translate data passed from the VM
let instruction = helper.translate_instruction(instruction_addr, ro_regions)?;
let message = Message::new(&[instruction]);
let program_id_index = message.instructions[0].program_id_index as usize;
let program_id = message.account_keys[program_id_index];
let (accounts, refs) = helper.translate_accounts(
&message,
account_infos_addr,
account_infos_len as usize,
ro_regions,
rw_regions,
)?;
let signers = helper.translate_signers(
&program_id,
signers_seeds_addr,
signers_seeds_len as usize,
ro_regions,
)?;
// Process instruction
let program_account = (*accounts[program_id_index]).clone();
if program_account.borrow().owner != bpf_loader::id() {
// Only BPF programs supported for now
return Err(HelperError::ProgramNotSupported.into());
}
let executable_accounts = vec![(program_id, program_account)];
#[allow(clippy::deref_addrof)]
match MessageProcessor::process_cross_program_instruction(
&message,
&executable_accounts,
&accounts,
&signers,
crate::process_instruction,
*(&mut *invoke_context),
) {
Ok(()) => (),
Err(err) => match ProgramError::try_from(err) {
Ok(err) => return Ok(err.into()),
Err(err) => return Err(HelperError::InstructionError(err).into()),
},
}
// Copy results back into caller's AccountInfos
for (i, (account, (lamport_ref, data))) in accounts.iter().zip(refs).enumerate() {
let account = account.borrow();
if message.is_writable(i) && !account.executable {
*lamport_ref = account.lamports;
data.clone_from_slice(&account.data);
}
}
Ok(SUCCESS)
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use solana_sdk::{
instruction::{AccountMeta, Instruction},
pubkey::Pubkey,
};
#[test] #[test]
fn test_translate() { fn test_translate() {

View File

@ -16,6 +16,7 @@ use solana_sdk::{
account::KeyedAccount, account::KeyedAccount,
bpf_loader, bpf_loader,
entrypoint::SUCCESS, entrypoint::SUCCESS,
entrypoint_native::InvokeContext,
instruction::InstructionError, instruction::InstructionError,
loader_instruction::LoaderInstruction, loader_instruction::LoaderInstruction,
program_utils::DecodeError, program_utils::DecodeError,
@ -54,13 +55,16 @@ pub enum BPFError {
} }
impl UserDefinedError for BPFError {} impl UserDefinedError for BPFError {}
pub fn create_vm(prog: &[u8]) -> Result<(EbpfVm<BPFError>, MemoryRegion), EbpfError<BPFError>> { pub fn create_vm<'a>(
prog: &'a [u8],
invoke_context: &'a mut dyn InvokeContext,
) -> Result<(EbpfVm<'a, BPFError>, MemoryRegion), EbpfError<BPFError>> {
let mut vm = EbpfVm::new(None)?; let mut vm = EbpfVm::new(None)?;
vm.set_verifier(bpf_verifier::check)?; vm.set_verifier(bpf_verifier::check)?;
vm.set_max_instruction_count(100_000)?; vm.set_max_instruction_count(100_000)?;
vm.set_elf(&prog)?; vm.set_elf(&prog)?;
let heap_region = helpers::register_helpers(&mut vm)?; let heap_region = helpers::register_helpers(&mut vm, invoke_context)?;
Ok((vm, heap_region)) Ok((vm, heap_region))
} }
@ -155,6 +159,7 @@ pub fn process_instruction(
program_id: &Pubkey, program_id: &Pubkey,
keyed_accounts: &[KeyedAccount], keyed_accounts: &[KeyedAccount],
instruction_data: &[u8], instruction_data: &[u8],
invoke_context: &mut dyn InvokeContext,
) -> Result<(), InstructionError> { ) -> Result<(), InstructionError> {
solana_logger::setup_with_default("solana=info"); solana_logger::setup_with_default("solana=info");
@ -177,7 +182,7 @@ pub fn process_instruction(
)?; )?;
{ {
let program_account = program.try_account_ref_mut()?; let program_account = program.try_account_ref_mut()?;
let (mut vm, heap_region) = match create_vm(&program_account.data) { let (mut vm, heap_region) = match create_vm(&program_account.data, invoke_context) {
Ok(info) => info, Ok(info) => info,
Err(e) => { Err(e) => {
warn!("Failed to create BPF VM: {}", e); warn!("Failed to create BPF VM: {}", e);
@ -250,8 +255,28 @@ pub fn process_instruction(
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use solana_sdk::{account::Account, rent::Rent}; use solana_sdk::{
use std::{fs::File, io::Read}; account::Account, instruction::CompiledInstruction, message::Message, rent::Rent,
};
use std::{cell::RefCell, fs::File, io::Read, rc::Rc};
#[derive(Debug, Default)]
pub struct MockInvokeContext {}
impl InvokeContext for MockInvokeContext {
fn push(&mut self, _key: &Pubkey) -> Result<(), InstructionError> {
Ok(())
}
fn pop(&mut self) {}
fn verify_and_update(
&mut self,
_message: &Message,
_instruction: &CompiledInstruction,
_signers: &[Pubkey],
_accounts: &[Rc<RefCell<Account>>],
) -> Result<(), InstructionError> {
Ok(())
}
}
#[test] #[test]
#[should_panic(expected = "ExceededMaxInstructions(10)")] #[should_panic(expected = "ExceededMaxInstructions(10)")]
@ -286,13 +311,23 @@ mod tests {
// Case: Empty keyed accounts // Case: Empty keyed accounts
assert_eq!( assert_eq!(
Err(InstructionError::NotEnoughAccountKeys), Err(InstructionError::NotEnoughAccountKeys),
process_instruction(&bpf_loader::id(), &vec![], &instruction_data) process_instruction(
&bpf_loader::id(),
&vec![],
&instruction_data,
&mut MockInvokeContext::default()
)
); );
// Case: Not signed // Case: Not signed
assert_eq!( assert_eq!(
Err(InstructionError::MissingRequiredSignature), Err(InstructionError::MissingRequiredSignature),
process_instruction(&bpf_loader::id(), &keyed_accounts, &instruction_data) process_instruction(
&bpf_loader::id(),
&keyed_accounts,
&instruction_data,
&mut MockInvokeContext::default()
)
); );
// Case: Write bytes to an offset // Case: Write bytes to an offset
@ -300,7 +335,12 @@ mod tests {
keyed_accounts[0].account.borrow_mut().data = vec![0; 6]; keyed_accounts[0].account.borrow_mut().data = vec![0; 6];
assert_eq!( assert_eq!(
Ok(()), Ok(()),
process_instruction(&bpf_loader::id(), &keyed_accounts, &instruction_data) process_instruction(
&bpf_loader::id(),
&keyed_accounts,
&instruction_data,
&mut MockInvokeContext::default()
)
); );
assert_eq!( assert_eq!(
vec![0, 0, 0, 1, 2, 3], vec![0, 0, 0, 1, 2, 3],
@ -312,7 +352,12 @@ mod tests {
keyed_accounts[0].account.borrow_mut().data = vec![0; 5]; keyed_accounts[0].account.borrow_mut().data = vec![0; 5];
assert_eq!( assert_eq!(
Err(InstructionError::AccountDataTooSmall), Err(InstructionError::AccountDataTooSmall),
process_instruction(&bpf_loader::id(), &keyed_accounts, &instruction_data) process_instruction(
&bpf_loader::id(),
&keyed_accounts,
&instruction_data,
&mut MockInvokeContext::default()
)
); );
} }
@ -332,20 +377,35 @@ mod tests {
// Case: Empty keyed accounts // Case: Empty keyed accounts
assert_eq!( assert_eq!(
Err(InstructionError::NotEnoughAccountKeys), Err(InstructionError::NotEnoughAccountKeys),
process_instruction(&bpf_loader::id(), &vec![], &instruction_data) process_instruction(
&bpf_loader::id(),
&vec![],
&instruction_data,
&mut MockInvokeContext::default()
)
); );
// Case: Not signed // Case: Not signed
assert_eq!( assert_eq!(
Err(InstructionError::MissingRequiredSignature), Err(InstructionError::MissingRequiredSignature),
process_instruction(&bpf_loader::id(), &keyed_accounts, &instruction_data) process_instruction(
&bpf_loader::id(),
&keyed_accounts,
&instruction_data,
&mut MockInvokeContext::default()
)
); );
// Case: Finalize // Case: Finalize
let keyed_accounts = vec![KeyedAccount::new(&program_key, true, &program_account)]; let keyed_accounts = vec![KeyedAccount::new(&program_key, true, &program_account)];
assert_eq!( assert_eq!(
Ok(()), Ok(()),
process_instruction(&bpf_loader::id(), &keyed_accounts, &instruction_data) process_instruction(
&bpf_loader::id(),
&keyed_accounts,
&instruction_data,
&mut MockInvokeContext::default()
)
); );
assert!(keyed_accounts[0].account.borrow().executable); assert!(keyed_accounts[0].account.borrow().executable);
@ -356,7 +416,12 @@ mod tests {
let keyed_accounts = vec![KeyedAccount::new(&program_key, true, &program_account)]; let keyed_accounts = vec![KeyedAccount::new(&program_key, true, &program_account)];
assert_eq!( assert_eq!(
Err(InstructionError::InvalidAccountData), Err(InstructionError::InvalidAccountData),
process_instruction(&bpf_loader::id(), &keyed_accounts, &instruction_data) process_instruction(
&bpf_loader::id(),
&keyed_accounts,
&instruction_data,
&mut MockInvokeContext::default()
)
); );
} }
@ -380,20 +445,35 @@ mod tests {
// Case: Empty keyed accounts // Case: Empty keyed accounts
assert_eq!( assert_eq!(
Err(InstructionError::NotEnoughAccountKeys), Err(InstructionError::NotEnoughAccountKeys),
process_instruction(&bpf_loader::id(), &vec![], &vec![]) process_instruction(
&bpf_loader::id(),
&vec![],
&vec![],
&mut MockInvokeContext::default()
)
); );
// Case: Only a program account // Case: Only a program account
assert_eq!( assert_eq!(
Ok(()), Ok(()),
process_instruction(&bpf_loader::id(), &keyed_accounts, &vec![]) process_instruction(
&bpf_loader::id(),
&keyed_accounts,
&vec![],
&mut MockInvokeContext::default()
)
); );
// Case: Account not executable // Case: Account not executable
keyed_accounts[0].account.borrow_mut().executable = false; keyed_accounts[0].account.borrow_mut().executable = false;
assert_eq!( assert_eq!(
Err(InstructionError::InvalidInstructionData), Err(InstructionError::InvalidInstructionData),
process_instruction(&bpf_loader::id(), &keyed_accounts, &vec![]) process_instruction(
&bpf_loader::id(),
&keyed_accounts,
&vec![],
&mut MockInvokeContext::default()
)
); );
keyed_accounts[0].account.borrow_mut().executable = true; keyed_accounts[0].account.borrow_mut().executable = true;
@ -402,7 +482,12 @@ mod tests {
keyed_accounts.push(KeyedAccount::new(&program_key, false, &parameter_account)); keyed_accounts.push(KeyedAccount::new(&program_key, false, &parameter_account));
assert_eq!( assert_eq!(
Ok(()), Ok(()),
process_instruction(&bpf_loader::id(), &keyed_accounts, &vec![]) process_instruction(
&bpf_loader::id(),
&keyed_accounts,
&vec![],
&mut MockInvokeContext::default()
)
); );
// Case: With duplicate accounts // Case: With duplicate accounts
@ -413,7 +498,12 @@ mod tests {
keyed_accounts.push(KeyedAccount::new(&duplicate_key, false, &parameter_account)); keyed_accounts.push(KeyedAccount::new(&duplicate_key, false, &parameter_account));
assert_eq!( assert_eq!(
Ok(()), Ok(()),
process_instruction(&bpf_loader::id(), &keyed_accounts, &vec![]) process_instruction(
&bpf_loader::id(),
&keyed_accounts,
&vec![],
&mut MockInvokeContext::default()
)
); );
} }
} }

View File

@ -8,6 +8,7 @@ use serde_derive::{Deserialize, Serialize};
use solana_sdk::{ use solana_sdk::{
account::KeyedAccount, account::KeyedAccount,
account_utils::State, account_utils::State,
entrypoint_native::InvokeContext,
instruction::InstructionError, instruction::InstructionError,
move_loader::id, move_loader::id,
program_utils::DecodeError, program_utils::DecodeError,
@ -445,6 +446,7 @@ impl MoveProcessor {
_program_id: &Pubkey, _program_id: &Pubkey,
keyed_accounts: &[KeyedAccount], keyed_accounts: &[KeyedAccount],
instruction_data: &[u8], instruction_data: &[u8],
_invoke_context: &mut dyn InvokeContext,
) -> Result<(), InstructionError> { ) -> Result<(), InstructionError> {
solana_logger::setup(); solana_logger::setup();

View File

@ -3,8 +3,8 @@
extern crate test; extern crate test;
use log::*; use log::*;
use solana_runtime::{message_processor::PreAccount, rent_collector::RentCollector}; use solana_runtime::message_processor::PreAccount;
use solana_sdk::{account::Account, pubkey::Pubkey}; use solana_sdk::{account::Account, pubkey::Pubkey, rent::Rent};
use test::Bencher; use test::Bencher;
#[bench] #[bench]
@ -13,28 +13,36 @@ fn bench_verify_account_changes_data(bencher: &mut Bencher) {
let owner = Pubkey::new_rand(); let owner = Pubkey::new_rand();
let non_owner = Pubkey::new_rand(); let non_owner = Pubkey::new_rand();
let pre = PreAccount::new(&Account::new(0, BUFSIZE, &owner), &owner, true); let pre = PreAccount::new(
&Pubkey::new_rand(),
&Account::new(0, BUFSIZE, &owner),
true,
false,
);
let post = Account::new(0, BUFSIZE, &owner); let post = Account::new(0, BUFSIZE, &owner);
assert_eq!(pre.verify(&owner, &RentCollector::default(), &post), Ok(())); assert_eq!(pre.verify(&owner, &Rent::default(), &post), Ok(()));
// this one should be faster // this one should be faster
bencher.iter(|| { bencher.iter(|| {
pre.verify(&owner, &RentCollector::default(), &post) pre.verify(&owner, &Rent::default(), &post).unwrap();
.unwrap();
}); });
let summary = bencher.bench(|_bencher| {}).unwrap(); let summary = bencher.bench(|_bencher| {}).unwrap();
info!("data no change by owner: {} ns/iter", summary.median); info!("data no change by owner: {} ns/iter", summary.median);
let pre = PreAccount::new(&Account::new(0, BUFSIZE, &owner), &non_owner, true); let pre_data = vec![BUFSIZE];
match pre.data { let post_data = vec![BUFSIZE];
Some(ref data) => bencher.iter(|| *data == post.data), bencher.iter(|| pre_data == post_data);
None => panic!("No data!"),
}
let summary = bencher.bench(|_bencher| {}).unwrap(); let summary = bencher.bench(|_bencher| {}).unwrap();
info!("data compare {} ns/iter", summary.median); info!("data compare {} ns/iter", summary.median);
let pre = PreAccount::new(
&Pubkey::new_rand(),
&Account::new(0, BUFSIZE, &owner),
true,
false,
);
bencher.iter(|| { bencher.iter(|| {
pre.verify(&non_owner, &RentCollector::default(), &post) pre.verify(&non_owner, &Rent::default(), &post).unwrap();
.unwrap();
}); });
let summary = bencher.bench(|_bencher| {}).unwrap(); let summary = bencher.bench(|_bencher| {}).unwrap();
info!("data no change by non owner: {} ns/iter", summary.median); info!("data no change by non owner: {} ns/iter", summary.median);

View File

@ -3,10 +3,12 @@ use serde::{Deserialize, Serialize};
use solana_sdk::{ use solana_sdk::{
account::{create_keyed_readonly_accounts, Account, KeyedAccount}, account::{create_keyed_readonly_accounts, Account, KeyedAccount},
clock::Epoch, clock::Epoch,
entrypoint_native::InvokeContext,
instruction::{CompiledInstruction, InstructionError}, instruction::{CompiledInstruction, InstructionError},
message::Message, message::Message,
native_loader, native_loader,
pubkey::Pubkey, pubkey::Pubkey,
rent::Rent,
system_program, system_program,
transaction::TransactionError, transaction::TransactionError,
}; };
@ -14,56 +16,35 @@ use std::{cell::RefCell, rc::Rc};
// The relevant state of an account before an Instruction executes, used // The relevant state of an account before an Instruction executes, used
// to verify account integrity after the Instruction completes // to verify account integrity after the Instruction completes
#[derive(Debug)] #[derive(Clone, Debug, Default)]
pub struct PreAccount { pub struct PreAccount {
pub is_writable: bool, key: Pubkey,
pub lamports: u64, is_signer: bool,
pub data_len: usize, is_writable: bool,
pub data: Option<Vec<u8>>, is_executable: bool,
pub owner: Pubkey, lamports: u64,
pub is_executable: bool, data: Vec<u8>,
pub rent_epoch: Epoch, owner: Pubkey,
rent_epoch: Epoch,
} }
impl PreAccount { impl PreAccount {
pub fn new(account: &Account, program_id: &Pubkey, is_writable: bool) -> Self { pub fn new(key: &Pubkey, account: &Account, is_signer: bool, is_writable: bool) -> Self {
Self { Self {
key: *key,
is_signer,
is_writable, is_writable,
lamports: account.lamports, lamports: account.lamports,
data_len: account.data.len(), data: account.data.clone(),
data: if Self::should_verify_data(
&account.owner,
program_id,
is_writable,
account.executable,
) {
Some(account.data.clone())
} else {
None
},
owner: account.owner, owner: account.owner,
is_executable: account.executable, is_executable: account.executable,
rent_epoch: account.rent_epoch, rent_epoch: account.rent_epoch,
} }
} }
fn should_verify_data(
owner: &Pubkey,
program_id: &Pubkey,
is_writable: bool,
is_executable: bool,
) -> bool {
// For accounts not assigned to the program, the data may not change.
program_id != owner
// Read-only account data may not change.
|| !is_writable
// Executable account data may not change.
|| is_executable
}
pub fn verify( pub fn verify(
&self, &self,
program_id: &Pubkey, program_id: &Pubkey,
rent_collector: &RentCollector, rent: &Rent,
post: &Account, post: &Account,
) -> Result<(), InstructionError> { ) -> Result<(), InstructionError> {
// Only the owner of the account may change owner and // Only the owner of the account may change owner and
@ -71,7 +52,7 @@ impl PreAccount {
// only if the data is zero-initialized or empty // only if the data is zero-initialized or empty
if self.owner != post.owner if self.owner != post.owner
&& (!self.is_writable // line coverage used to get branch coverage && (!self.is_writable // line coverage used to get branch coverage
|| *program_id != self.owner // line coverage used to get branch coverage || *program_id != self.owner
|| !Self::is_zeroed(&post.data)) || !Self::is_zeroed(&post.data))
{ {
return Err(InstructionError::ModifiedProgramId); return Err(InstructionError::ModifiedProgramId);
@ -96,43 +77,37 @@ impl PreAccount {
// Only the system program can change the size of the data // Only the system program can change the size of the data
// and only if the system program owns the account // and only if the system program owns the account
if self.data_len != post.data.len() if self.data.len() != post.data.len()
&& (!system_program::check_id(program_id) // line coverage used to get branch coverage && (!system_program::check_id(program_id) // line coverage used to get branch coverage
|| !system_program::check_id(&self.owner)) || !system_program::check_id(&self.owner))
{ {
return Err(InstructionError::AccountDataSizeChanged); return Err(InstructionError::AccountDataSizeChanged);
} }
if Self::should_verify_data( // Only the owner may change account data
&self.owner, // and if the account is writable
program_id, // and if the account is not executable
self.is_writable, if !(*program_id == self.owner
self.is_executable, && self.is_writable // line coverage used to get branch coverage
) { && !self.is_executable)
match &self.data { && self.data != post.data
Some(data) if *data == post.data => (), {
_ => { if self.is_executable {
if self.is_executable { return Err(InstructionError::ExecutableDataModified);
return Err(InstructionError::ExecutableDataModified); } else if self.is_writable {
} else if self.is_writable { return Err(InstructionError::ExternalAccountDataModified);
return Err(InstructionError::ExternalAccountDataModified); } else {
} else { return Err(InstructionError::ReadonlyDataModified);
return Err(InstructionError::ReadonlyDataModified);
}
}
} }
} }
// executable is one-way (false->true) and only the account owner may set it. // executable is one-way (false->true) and only the account owner may set it.
if self.is_executable != post.executable { if self.is_executable != post.executable {
if !rent_collector if !rent.is_exempt(post.lamports, post.data.len()) {
.rent
.is_exempt(post.lamports, post.data.len())
{
return Err(InstructionError::ExecutableAccountNotRentExempt); return Err(InstructionError::ExecutableAccountNotRentExempt);
} }
if !self.is_writable // line coverage used to get branch coverage if !self.is_writable // line coverage used to get branch coverage
|| self.is_executable // line coverage used to get branch coverage || self.is_executable
|| *program_id != self.owner || *program_id != self.owner
{ {
return Err(InstructionError::ExecutableModified); return Err(InstructionError::ExecutableModified);
@ -147,6 +122,50 @@ impl PreAccount {
Ok(()) Ok(())
} }
pub fn verify_cross_program(
&self,
is_writable: bool,
is_signer: bool,
signers: &[Pubkey],
program_id: &Pubkey,
rent: &Rent,
post: &Account,
) -> Result<(), InstructionError> {
// Readonly account cannot become writable
if is_writable && !self.is_writable {
return Err(InstructionError::WritableModified);
}
if is_signer && // If message indicates account is signed
!( // one of the following needs to be true:
self.is_signer // Signed in the original transaction
|| signers.contains(&self.key) // Signed by the program
) {
return Err(InstructionError::SignerModified);
}
self.verify(program_id, rent, post)
}
pub fn update(&mut self, account: &Account) {
self.lamports = account.lamports;
if self.data.len() != account.data.len() {
// Only system account can change data size, copy with alloc
self.data = account.data.clone();
} else {
// Copy without allocate
self.data.clone_from_slice(&account.data);
}
}
pub fn key(&self) -> Pubkey {
self.key
}
pub fn lamports(&self) -> u64 {
self.lamports
}
pub fn is_zeroed(buf: &[u8]) -> bool { pub fn is_zeroed(buf: &[u8]) -> bool {
const ZEROS_LEN: usize = 1024; const ZEROS_LEN: usize = 1024;
static ZEROS: [u8; ZEROS_LEN] = [0; ZEROS_LEN]; static ZEROS: [u8; ZEROS_LEN] = [0; ZEROS_LEN];
@ -157,7 +176,64 @@ impl PreAccount {
} }
} }
#[derive(Debug, Default)]
pub struct ThisInvokeContext {
pub program_ids: Vec<Pubkey>,
pub rent: Rent,
pub pre_accounts: Vec<PreAccount>,
}
impl ThisInvokeContext {
const MAX_INVOCATION_DEPTH: usize = 5;
pub fn new(program_id: &Pubkey, rent: Rent, pre_accounts: Vec<PreAccount>) -> Self {
let mut program_ids = Vec::with_capacity(Self::MAX_INVOCATION_DEPTH);
program_ids.push(*program_id);
Self {
program_ids,
rent,
pre_accounts,
}
}
}
impl InvokeContext for ThisInvokeContext {
fn push(&mut self, key: &Pubkey) -> Result<(), InstructionError> {
if self.program_ids.len() >= Self::MAX_INVOCATION_DEPTH {
return Err(InstructionError::CallDepth);
}
if self.program_ids.contains(key) && self.program_ids.last() != Some(key) {
// Reentrancy not allowed unless caller is calling itself
return Err(InstructionError::ReentrancyNotAllowed);
}
self.program_ids.push(*key);
Ok(())
}
fn pop(&mut self) {
self.program_ids.pop();
}
fn verify_and_update(
&mut self,
message: &Message,
instruction: &CompiledInstruction,
signers: &[Pubkey],
accounts: &[Rc<RefCell<Account>>],
) -> Result<(), InstructionError> {
match self.program_ids.last() {
Some(key) => MessageProcessor::verify_and_update(
message,
instruction,
&mut self.pre_accounts,
key,
&self.rent,
signers,
accounts,
),
None => Err(InstructionError::GenericError), // Should never happen
}
}
}
pub type ProcessInstruction = fn(&Pubkey, &[KeyedAccount], &[u8]) -> Result<(), InstructionError>; pub type ProcessInstruction = fn(&Pubkey, &[KeyedAccount], &[u8]) -> Result<(), InstructionError>;
pub type ProcessInstructionWithContext =
fn(&Pubkey, &[KeyedAccount], &[u8], &mut dyn InvokeContext) -> Result<(), InstructionError>;
#[derive(Default, Deserialize, Serialize)] #[derive(Default, Deserialize, Serialize)]
pub struct MessageProcessor { pub struct MessageProcessor {
@ -193,16 +269,14 @@ impl MessageProcessor {
} }
} }
/// Process an instruction /// Create the KeyedAccounts that will be passed to the program
/// This method calls the instruction's program entrypoint method fn create_keyed_accounts<'a>(
fn process_instruction( message: &'a Message,
&self, instruction: &'a CompiledInstruction,
message: &Message, executable_accounts: &'a [(Pubkey, RefCell<Account>)],
instruction: &CompiledInstruction, accounts: &'a [Rc<RefCell<Account>>],
executable_accounts: &[(Pubkey, RefCell<Account>)], ) -> Result<Vec<KeyedAccount<'a>>, InstructionError> {
accounts: &[Rc<RefCell<Account>>], let mut keyed_accounts = create_keyed_readonly_accounts(&executable_accounts);
) -> Result<(), InstructionError> {
let mut keyed_accounts = create_keyed_readonly_accounts(executable_accounts);
let mut keyed_accounts2: Vec<_> = instruction let mut keyed_accounts2: Vec<_> = instruction
.accounts .accounts
.iter() .iter()
@ -220,6 +294,21 @@ impl MessageProcessor {
.collect(); .collect();
keyed_accounts.append(&mut keyed_accounts2); keyed_accounts.append(&mut keyed_accounts2);
assert!(keyed_accounts[0].executable()?, "account not executable"); assert!(keyed_accounts[0].executable()?, "account not executable");
Ok(keyed_accounts)
}
/// Process an instruction
/// This method calls the instruction's program entrypoint method
fn process_instruction(
&self,
message: &Message,
instruction: &CompiledInstruction,
invoke_context: &mut dyn InvokeContext,
executable_accounts: &[(Pubkey, RefCell<Account>)],
accounts: &[Rc<RefCell<Account>>],
) -> Result<(), InstructionError> {
let keyed_accounts =
Self::create_keyed_accounts(message, instruction, executable_accounts, accounts)?;
for (id, process_instruction) in &self.instruction_processors { for (id, process_instruction) in &self.instruction_processors {
let root_program_id = keyed_accounts[0].unsigned_key(); let root_program_id = keyed_accounts[0].unsigned_key();
@ -237,12 +326,49 @@ impl MessageProcessor {
&native_loader::id(), &native_loader::id(),
&keyed_accounts, &keyed_accounts,
&instruction.data, &instruction.data,
invoke_context,
) )
} else { } else {
Err(InstructionError::UnsupportedProgramId) Err(InstructionError::UnsupportedProgramId)
} }
} }
/// Process a cross-program instruction
/// This method calls the instruction's program entrypoint method
pub fn process_cross_program_instruction(
message: &Message,
executable_accounts: &[(Pubkey, RefCell<Account>)],
accounts: &[Rc<RefCell<Account>>],
signers: &[Pubkey],
process_instruction: ProcessInstructionWithContext,
invoke_context: &mut dyn InvokeContext,
) -> Result<(), InstructionError> {
let instruction = &message.instructions[0];
// Verify the calling program hasn't misbehaved
invoke_context.verify_and_update(message, instruction, signers, accounts)?;
// Construct keyed accounts
let keyed_accounts =
Self::create_keyed_accounts(message, instruction, executable_accounts, accounts)?;
// Invoke callee
invoke_context.push(instruction.program_id(&message.account_keys))?;
let mut result = process_instruction(
&keyed_accounts[0].owner()?,
&keyed_accounts,
&instruction.data,
invoke_context,
);
if result.is_ok() {
// Verify the called program has not misbehaved
result = invoke_context.verify_and_update(message, instruction, signers, accounts);
}
invoke_context.pop();
result
}
/// Record the initial state of the accounts so that they can be compared /// Record the initial state of the accounts so that they can be compared
/// after the instruction is processed /// after the instruction is processed
pub fn create_pre_accounts( pub fn create_pre_accounts(
@ -252,11 +378,12 @@ impl MessageProcessor {
) -> Vec<PreAccount> { ) -> Vec<PreAccount> {
let mut pre_accounts = Vec::with_capacity(accounts.len()); let mut pre_accounts = Vec::with_capacity(accounts.len());
{ {
let program_id = instruction.program_id(&message.account_keys);
let mut work = |_unique_index: usize, account_index: usize| { let mut work = |_unique_index: usize, account_index: usize| {
let key = &message.account_keys[account_index];
let is_signer = account_index < message.header.num_required_signatures as usize;
let is_writable = message.is_writable(account_index); let is_writable = message.is_writable(account_index);
let account = accounts[account_index].borrow(); let account = accounts[account_index].borrow();
pre_accounts.push(PreAccount::new(&account, program_id, is_writable)); pre_accounts.push(PreAccount::new(key, &account, is_signer, is_writable));
Ok(()) Ok(())
}; };
let _ = instruction.visit_each_account(&mut work); let _ = instruction.visit_each_account(&mut work);
@ -266,15 +393,9 @@ impl MessageProcessor {
/// Verify there are no outstanding borrows /// Verify there are no outstanding borrows
pub fn verify_account_references( pub fn verify_account_references(
executable_accounts: &[(Pubkey, RefCell<Account>)], accounts: &[(Pubkey, RefCell<Account>)],
accounts: &[Rc<RefCell<Account>>],
) -> Result<(), InstructionError> { ) -> Result<(), InstructionError> {
for account in accounts.iter() { for (_, account) in accounts.iter() {
account
.try_borrow_mut()
.map_err(|_| InstructionError::AccountBorrowOutstanding)?;
}
for (_, account) in executable_accounts.iter() {
account account
.try_borrow_mut() .try_borrow_mut()
.map_err(|_| InstructionError::AccountBorrowOutstanding)?; .map_err(|_| InstructionError::AccountBorrowOutstanding)?;
@ -291,17 +412,20 @@ impl MessageProcessor {
accounts: &[Rc<RefCell<Account>>], accounts: &[Rc<RefCell<Account>>],
rent_collector: &RentCollector, rent_collector: &RentCollector,
) -> Result<(), InstructionError> { ) -> Result<(), InstructionError> {
// Verify all accounts have zero outstanding refs // Verify all executable accounts have zero outstanding refs
Self::verify_account_references(executable_accounts, accounts)?; Self::verify_account_references(executable_accounts)?;
// Verify the per-account instruction results // Verify the per-account instruction results
let (mut pre_sum, mut post_sum) = (0_u128, 0_u128); let (mut pre_sum, mut post_sum) = (0_u128, 0_u128);
{ {
let program_id = instruction.program_id(&message.account_keys); let program_id = instruction.program_id(&message.account_keys);
let mut work = |unique_index: usize, account_index: usize| { let mut work = |unique_index: usize, account_index: usize| {
let account = accounts[account_index].borrow(); // Verify account has no outstanding references and take one
pre_accounts[unique_index].verify(&program_id, rent_collector, &account)?; let account = accounts[account_index]
pre_sum += u128::from(pre_accounts[unique_index].lamports); .try_borrow_mut()
.map_err(|_| InstructionError::AccountBorrowOutstanding)?;
pre_accounts[unique_index].verify(&program_id, &rent_collector.rent, &account)?;
pre_sum += u128::from(pre_accounts[unique_index].lamports());
post_sum += u128::from(account.lamports); post_sum += u128::from(account.lamports);
Ok(()) Ok(())
}; };
@ -315,6 +439,56 @@ impl MessageProcessor {
Ok(()) Ok(())
} }
/// Verify the results of a cross-program instruction
fn verify_and_update(
message: &Message,
instruction: &CompiledInstruction,
pre_accounts: &mut [PreAccount],
program_id: &Pubkey,
rent: &Rent,
signers: &[Pubkey],
accounts: &[Rc<RefCell<Account>>],
) -> Result<(), InstructionError> {
// Verify the per-account instruction results
let (mut pre_sum, mut post_sum) = (0_u128, 0_u128);
let mut work = |_unique_index: usize, account_index: usize| {
let key = &message.account_keys[account_index];
let account = &accounts[account_index];
// Find the matching PreAccount
for pre_account in pre_accounts.iter_mut() {
if *key == pre_account.key() {
// Verify account has no outstanding references and take one
let account = account
.try_borrow_mut()
.map_err(|_| InstructionError::AccountBorrowOutstanding)?;
pre_account.verify_cross_program(
message.is_writable(account_index),
message.is_signer(account_index),
signers,
&program_id,
&rent,
&account,
)?;
pre_sum += u128::from(pre_account.lamports());
post_sum += u128::from(account.lamports);
pre_account.update(&account);
return Ok(());
}
}
Err(InstructionError::MissingAccount)
};
instruction.visit_each_account(&mut work)?;
// Verify that the total sum of all the lamports did not change
if pre_sum != post_sum {
return Err(InstructionError::UnbalancedInstruction);
}
Ok(())
}
/// Execute an instruction /// Execute an instruction
/// This method calls the instruction's program entrypoint method and verifies that the result of /// This method calls the instruction's program entrypoint method and verifies that the result of
/// the call does not violate the bank's accounting rules. /// the call does not violate the bank's accounting rules.
@ -328,11 +502,22 @@ impl MessageProcessor {
rent_collector: &RentCollector, rent_collector: &RentCollector,
) -> Result<(), InstructionError> { ) -> Result<(), InstructionError> {
let pre_accounts = Self::create_pre_accounts(message, instruction, accounts); let pre_accounts = Self::create_pre_accounts(message, instruction, accounts);
self.process_instruction(message, instruction, executable_accounts, accounts)?; let mut invoke_context = ThisInvokeContext::new(
instruction.program_id(&message.account_keys),
rent_collector.rent,
pre_accounts,
);
self.process_instruction(
message,
instruction,
&mut invoke_context,
executable_accounts,
accounts,
)?;
Self::verify( Self::verify(
message, message,
instruction, instruction,
&pre_accounts, &invoke_context.pre_accounts,
executable_accounts, executable_accounts,
accounts, accounts,
rent_collector, rent_collector,
@ -378,6 +563,86 @@ mod tests {
native_loader::create_loadable_account, native_loader::create_loadable_account,
}; };
#[test]
fn test_invoke_context() {
const MAX_DEPTH: usize = 10;
let mut program_ids = vec![];
let mut keys = vec![];
let mut pre_accounts = vec![];
let mut accounts = vec![];
for i in 0..MAX_DEPTH {
program_ids.push(Pubkey::new_rand());
keys.push(Pubkey::new_rand());
accounts.push(Rc::new(RefCell::new(Account::new(
i as u64,
1,
&program_ids[i],
))));
pre_accounts.push(PreAccount::new(
&keys[i],
&accounts[i].borrow(),
false,
true,
))
}
let mut invoke_context =
ThisInvokeContext::new(&program_ids[0], Rent::default(), pre_accounts);
// Check call depth increases and has a limit
let mut depth_reached = 1;
for i in 1..MAX_DEPTH {
if Err(InstructionError::CallDepth) == invoke_context.push(&program_ids[i]) {
break;
}
depth_reached += 1;
}
assert_ne!(depth_reached, 0);
assert!(depth_reached < MAX_DEPTH);
// Mock each invocation
for owned_index in (1..depth_reached).rev() {
let not_owned_index = owned_index - 1;
let metas = vec![
AccountMeta::new(keys[not_owned_index], false),
AccountMeta::new(keys[owned_index], false),
];
let message =
Message::new(&[Instruction::new(program_ids[owned_index], &[0_u8], metas)]);
// modify account owned by the program
accounts[owned_index].borrow_mut().data[0] = (MAX_DEPTH + owned_index) as u8;
invoke_context
.verify_and_update(
&message,
&message.instructions[0],
&[],
&accounts[not_owned_index..owned_index + 1],
)
.unwrap();
assert_eq!(
invoke_context.pre_accounts[owned_index].data[0],
(MAX_DEPTH + owned_index) as u8
);
// modify account not owned by the program
let data = accounts[not_owned_index].borrow_mut().data[0];
accounts[not_owned_index].borrow_mut().data[0] = (MAX_DEPTH + not_owned_index) as u8;
assert_eq!(
invoke_context.verify_and_update(
&message,
&message.instructions[0],
&[],
&accounts[not_owned_index..owned_index + 1],
),
Err(InstructionError::ExternalAccountDataModified)
);
assert_eq!(invoke_context.pre_accounts[not_owned_index].data[0], data);
accounts[not_owned_index].borrow_mut().data[0] = data;
invoke_context.pop();
}
}
#[test] #[test]
fn test_is_zeroed() { fn test_is_zeroed() {
const ZEROS_LEN: usize = 1024; const ZEROS_LEN: usize = 1024;
@ -402,51 +667,44 @@ mod tests {
#[test] #[test]
fn test_verify_account_references() { fn test_verify_account_references() {
let executable_accounts = vec![(Pubkey::new_rand(), RefCell::new(Account::default()))]; let accounts = vec![(Pubkey::new_rand(), RefCell::new(Account::default()))];
let program_accounts = vec![Rc::new(RefCell::new(Account::default()))];
assert!(MessageProcessor::verify_account_references( assert!(MessageProcessor::verify_account_references(&accounts).is_ok());
&executable_accounts,
&program_accounts,
)
.is_ok());
let cloned = program_accounts[0].clone(); let mut _borrowed = accounts[0].1.borrow();
let _borrowed = cloned.borrow();
assert_eq!( assert_eq!(
MessageProcessor::verify_account_references(&executable_accounts, &program_accounts,), MessageProcessor::verify_account_references(&accounts),
Err(InstructionError::AccountBorrowOutstanding)
);
let cloned = executable_accounts[0].1.clone();
let _borrowed = cloned.borrow();
assert_eq!(
MessageProcessor::verify_account_references(&executable_accounts, &program_accounts,),
Err(InstructionError::AccountBorrowOutstanding) Err(InstructionError::AccountBorrowOutstanding)
); );
} }
struct Change { struct Change<'a> {
// key: Pubkey,
program_id: Pubkey, program_id: Pubkey,
rent_collector: RentCollector, message_is_writable: bool,
message_is_signer: bool,
signers: &'a [Pubkey],
rent: Rent,
pre: PreAccount, pre: PreAccount,
post: Account, post: Account,
} }
impl Change { impl<'a> Change<'a> {
pub fn new(owner: &Pubkey, program_id: &Pubkey) -> Self { pub fn new(owner: &Pubkey, program_id: &Pubkey) -> Self {
Self { Self {
// key: Pubkey::new_rand(), // key: Pubkey::new_rand(),
program_id: *program_id, program_id: *program_id,
rent_collector: RentCollector::default(), message_is_writable: false,
message_is_signer: false,
signers: &[],
rent: Rent::default(),
pre: PreAccount::new( pre: PreAccount::new(
&Pubkey::new_rand(),
&Account { &Account {
owner: *owner, owner: *owner,
lamports: std::u64::MAX, lamports: std::u64::MAX,
data: vec![], data: vec![],
..Account::default() ..Account::default()
}, },
&Pubkey::new_rand(), false,
true, true,
), ),
post: Account { post: Account {
@ -456,10 +714,26 @@ mod tests {
}, },
} }
} }
pub fn new_cross_program(owner: &Pubkey, program_id: &Pubkey, key: &Pubkey) -> Self {
let mut change = Change::new(owner, program_id);
change.pre.key = key.clone();
change
}
pub fn read_only(mut self) -> Self { pub fn read_only(mut self) -> Self {
self.pre.is_writable = false; self.pre.is_writable = false;
self self
} }
pub fn writable(mut self, pre: bool, message_is_writable: bool) -> Self {
self.pre.is_writable = pre;
self.message_is_writable = message_is_writable;
self
}
pub fn signer(mut self, pre: bool, message_is_signer: bool, signers: &'a [Pubkey]) -> Self {
self.pre.is_signer = pre;
self.message_is_signer = message_is_signer;
self.signers = signers;
self
}
pub fn executable(mut self, pre: bool, post: bool) -> Self { pub fn executable(mut self, pre: bool, post: bool) -> Self {
self.pre.is_executable = pre; self.pre.is_executable = pre;
self.post.executable = post; self.post.executable = post;
@ -475,8 +749,7 @@ mod tests {
self self
} }
pub fn data(mut self, pre: Vec<u8>, post: Vec<u8>) -> Self { pub fn data(mut self, pre: Vec<u8>, post: Vec<u8>) -> Self {
self.pre.data_len = pre.len(); self.pre.data = pre;
self.pre.data = Some(pre);
self.post.data = post; self.post.data = post;
self self
} }
@ -486,8 +759,17 @@ mod tests {
self self
} }
pub fn verify(&self) -> Result<(), InstructionError> { pub fn verify(&self) -> Result<(), InstructionError> {
self.pre self.pre.verify(&self.program_id, &self.rent, &self.post)
.verify(&self.program_id, &self.rent_collector, &self.post) }
pub fn verify_cross_program(&self) -> Result<(), InstructionError> {
self.pre.verify_cross_program(
self.message_is_writable,
self.message_is_signer,
self.signers,
&self.program_id,
&self.rent,
&self.post,
)
} }
} }
@ -631,7 +913,7 @@ mod tests {
"owner should not be able to subtract lamports once marked executable" "owner should not be able to subtract lamports once marked executable"
); );
let data = vec![1; 100]; let data = vec![1; 100];
let min_lamports = RentCollector::default().rent.minimum_balance(data.len()); let min_lamports = Rent::default().minimum_balance(data.len());
assert_eq!( assert_eq!(
Change::new(&owner, &owner) Change::new(&owner, &owner)
.executable(false, true) .executable(false, true)
@ -651,6 +933,59 @@ mod tests {
); );
} }
#[test]
fn test_verify_account_changes_writable() {
let owner = Pubkey::new_rand();
let system_program_id = system_program::id();
assert_eq!(
Change::new(&owner, &system_program_id)
.writable(true, false)
.verify_cross_program(),
Ok(()),
"account can we changed to readonly"
);
assert_eq!(
Change::new(&owner, &system_program_id)
.writable(false, true)
.verify_cross_program(),
Err(InstructionError::WritableModified),
"account cannot be changed to writable"
);
}
#[test]
fn test_verify_account_changes_signer() {
let owner = Pubkey::new_rand();
let system_program_id = system_program::id();
let key = Pubkey::new_rand();
assert_eq!(
Change::new_cross_program(&owner, &system_program_id, &key)
.signer(false, true, &[key.clone()])
.verify_cross_program(),
Ok(()),
"account signed by a signer"
);
assert_eq!(
Change::new_cross_program(&owner, &system_program_id, &key)
.signer(false, true, &[])
.verify_cross_program(),
Err(InstructionError::SignerModified),
"account cannot be changed to signed if no signer"
);
assert_eq!(
Change::new_cross_program(&owner, &system_program_id, &key)
.signer(false, true, &[Pubkey::new_rand(), Pubkey::new_rand()])
.verify_cross_program(),
Err(InstructionError::SignerModified),
"account cannot be changed to signed if no signer exists"
);
}
#[test] #[test]
fn test_verify_account_changes_data_len() { fn test_verify_account_changes_data_len() {
let alice_program_id = Pubkey::new_rand(); let alice_program_id = Pubkey::new_rand();
@ -666,8 +1001,8 @@ mod tests {
Change::new(&alice_program_id, &system_program::id()) Change::new(&alice_program_id, &system_program::id())
.data(vec![0], vec![0,0]) .data(vec![0], vec![0,0])
.verify(), .verify(),
Err(InstructionError::AccountDataSizeChanged), Err(InstructionError::AccountDataSizeChanged),
"system program should not be able to change the data length of accounts it does not own" "system program should not be able to change the data length of accounts it does not own"
); );
} }
@ -730,9 +1065,9 @@ mod tests {
.lamports(42, 1) .lamports(42, 1)
.data(vec![42], vec![0]) .data(vec![42], vec![0])
.verify(), .verify(),
Ok(()), Ok(()),
"alice should be able to deduct lamports and give the account to bob if the data is zeroed", "alice should be able to deduct lamports and give the account to bob if the data is zeroed",
); );
} }
#[test] #[test]
@ -1023,4 +1358,125 @@ mod tests {
assert_eq!(accounts[1].borrow().lamports, 20); assert_eq!(accounts[1].borrow().lamports, 20);
assert_eq!(accounts[0].borrow().data, vec![42]); assert_eq!(accounts[0].borrow().data, vec![42]);
} }
#[test]
fn test_process_cross_program() {
#[derive(Serialize, Deserialize)]
enum MockInstruction {
NoopSuccess,
NoopFail,
ModifyOwned,
ModifyNotOwned,
}
fn mock_process_instruction(
program_id: &Pubkey,
keyed_accounts: &[KeyedAccount],
data: &[u8],
_invoke_context: &mut dyn InvokeContext,
) -> Result<(), InstructionError> {
assert_eq!(*program_id, keyed_accounts[0].owner()?);
assert_eq!(
keyed_accounts[1].owner()?,
*keyed_accounts[0].unsigned_key()
);
assert_ne!(
keyed_accounts[2].owner()?,
*keyed_accounts[0].unsigned_key()
);
if let Ok(instruction) = bincode::deserialize(data) {
match instruction {
MockInstruction::NoopSuccess => (),
MockInstruction::NoopFail => return Err(InstructionError::GenericError),
MockInstruction::ModifyOwned => {
keyed_accounts[1].try_account_ref_mut()?.data[0] = 1
}
MockInstruction::ModifyNotOwned => {
keyed_accounts[2].try_account_ref_mut()?.data[0] = 1
}
}
} else {
return Err(InstructionError::InvalidInstructionData);
}
Ok(())
}
let caller_program_id = Pubkey::new_rand();
let callee_program_id = Pubkey::new_rand();
let mut program_account = Account::new(1, 0, &Pubkey::new_rand());
program_account.executable = true;
let executable_accounts = vec![(callee_program_id, RefCell::new(program_account))];
let owned_key = Pubkey::new_rand();
let owned_account = Account::new(42, 1, &callee_program_id);
let owned_preaccount = PreAccount::new(&owned_key, &owned_account, false, true);
let not_owned_key = Pubkey::new_rand();
let not_owned_account = Account::new(84, 1, &Pubkey::new_rand());
let not_owned_preaccount = PreAccount::new(&not_owned_key, &not_owned_account, false, true);
let mut accounts = vec![
Rc::new(RefCell::new(owned_account)),
Rc::new(RefCell::new(not_owned_account)),
];
let mut invoke_context = ThisInvokeContext::new(
&caller_program_id,
Rent::default(),
vec![owned_preaccount, not_owned_preaccount],
);
let metas = vec![
AccountMeta::new(owned_key, false),
AccountMeta::new(not_owned_key, false),
];
// not owned account modified by the caller (before the invoke)
accounts[0].borrow_mut().data[0] = 1;
let message = Message::new(&[Instruction::new(
callee_program_id,
&MockInstruction::NoopSuccess,
metas.clone(),
)]);
assert_eq!(
MessageProcessor::process_cross_program_instruction(
&message,
&executable_accounts,
&accounts,
&[],
mock_process_instruction,
&mut invoke_context,
),
Err(InstructionError::ExternalAccountDataModified)
);
accounts[0].borrow_mut().data[0] = 0;
let cases = vec![
(MockInstruction::NoopSuccess, Ok(())),
(
MockInstruction::NoopFail,
Err(InstructionError::GenericError),
),
(MockInstruction::ModifyOwned, Ok(())),
(
MockInstruction::ModifyNotOwned,
Err(InstructionError::ExternalAccountDataModified),
),
];
for case in cases {
let message =
Message::new(&[Instruction::new(callee_program_id, &case.0, metas.clone())]);
assert_eq!(
MessageProcessor::process_cross_program_instruction(
&message,
&executable_accounts,
&accounts,
&[],
mock_process_instruction,
&mut invoke_context,
),
case.1
);
}
}
} }

View File

@ -7,7 +7,7 @@ use log::*;
use num_derive::{FromPrimitive, ToPrimitive}; use num_derive::{FromPrimitive, ToPrimitive};
use solana_sdk::{ use solana_sdk::{
account::KeyedAccount, account::KeyedAccount,
entrypoint_native::{LoaderEntrypoint, ProgramEntrypoint}, entrypoint_native::{InvokeContext, LoaderEntrypoint, ProgramEntrypoint},
instruction::InstructionError, instruction::InstructionError,
program_utils::{next_keyed_account, DecodeError}, program_utils::{next_keyed_account, DecodeError},
pubkey::Pubkey, pubkey::Pubkey,
@ -126,6 +126,7 @@ impl NativeLoader {
_program_id: &Pubkey, _program_id: &Pubkey,
keyed_accounts: &[KeyedAccount], keyed_accounts: &[KeyedAccount],
instruction_data: &[u8], instruction_data: &[u8],
invoke_context: &dyn InvokeContext,
) -> Result<(), InstructionError> { ) -> Result<(), InstructionError> {
let mut keyed_accounts_iter = keyed_accounts.iter(); let mut keyed_accounts_iter = keyed_accounts.iter();
let program = next_keyed_account(&mut keyed_accounts_iter)?; let program = next_keyed_account(&mut keyed_accounts_iter)?;
@ -142,7 +143,14 @@ impl NativeLoader {
if name.ends_with("loader_program") { if name.ends_with("loader_program") {
let entrypoint = let entrypoint =
Self::get_entrypoint::<LoaderEntrypoint>(name, &self.loader_symbol_cache)?; Self::get_entrypoint::<LoaderEntrypoint>(name, &self.loader_symbol_cache)?;
unsafe { entrypoint(program.unsigned_key(), params, instruction_data) } unsafe {
entrypoint(
program.unsigned_key(),
params,
instruction_data,
invoke_context,
)
}
} else { } else {
let entrypoint = let entrypoint =
Self::get_entrypoint::<ProgramEntrypoint>(name, &self.program_symbol_cache)?; Self::get_entrypoint::<ProgramEntrypoint>(name, &self.program_symbol_cache)?;

View File

@ -253,7 +253,7 @@ if (!(expr)) { \
*/ */
typedef struct { typedef struct {
SolAccountInfo* ka; /** Pointer to an array of SolAccountInfo, must already SolAccountInfo* ka; /** Pointer to an array of SolAccountInfo, must already
point to an array of SolAccountInfos */ point to an array of SolAccountInfos */
uint64_t ka_num; /** Number of SolAccountInfo entries in `ka` */ uint64_t ka_num; /** Number of SolAccountInfo entries in `ka` */
const uint8_t *data; /** pointer to the instruction data */ const uint8_t *data; /** pointer to the instruction data */
uint64_t data_len; /** Length in bytes of the instruction data */ uint64_t data_len; /** Length in bytes of the instruction data */
@ -364,6 +364,99 @@ SOL_FN_PREFIX bool sol_deserialize(
return true; return true;
} }
/**
* Account Meta
*/
typedef struct {
SolPubkey *pubkey; /** An account's public key */
bool is_writable; /** True if the `pubkey` can be loaded as a read-write account */
bool is_signer; /** True if an Instruction requires a Transaction signature matching `pubkey` */
} SolAccountMeta;
/**
* Instruction
*/
typedef struct {
SolPubkey *program_id; /** Pubkey of the instruction processor that executes this instruction */
SolAccountMeta *accounts; /** Metadata for what accounts should be passed to the instruction processor */
uint64_t account_len; /** Number of SolAccountMetas */
uint8_t *data; /** Opaque data passed to the instruction processor */
uint64_t data_len; /** Length of the data in bytes */
} SolInstruction;
/**
* Seed used to create a program address
*/
typedef struct {
const char *addr; /** Seed string */
uint64_t len; /** Length of the seed string */
} SolSignerSeed;
/**
* Seeds used by a signer to create a program address
*/
typedef struct {
const SolSignerSeed *addr; /** An arry of a signer's seeds */
uint64_t len; /** Number of seeds */
} SolSignerSeeds;
/**
* Cross-program invocation
* * @{
*/
/*
* @param instruction Instruction to process
* @param account_infos Accounts used by instruction
* @param account_infos_len Length of account_infos array
* @param seeds Seed strings used to sign program accounts
* @param seeds_len Length of the seeds array
*/
SOL_FN_PREFIX uint64_t sol_invoke_signed(
const SolInstruction *instruction,
const SolAccountInfo *account_infos,
int account_infos_len,
const SolSignerSeeds *signers_seeds,
int signers_seeds_len
) {
uint64_t sol_invoke_signed_c(
const SolInstruction *instruction,
const SolAccountInfo *account_infos,
int account_infos_len,
const SolSignerSeeds *signers_seeds,
int signers_seeds_len
);
return sol_invoke_signed_c(
instruction,
account_infos,
account_infos_len,
signers_seeds,
signers_seeds_len
);
}
/*
* @param instruction Instruction to process
* @param account_infos Accounts used by instruction
* @param account_infos_len Length of account_infos array
*/
SOL_FN_PREFIX uint64_t sol_invoke(
const SolInstruction *instruction,
const SolAccountInfo *account_infos,
int account_infos_len
) {
const SolSignerSeeds signers_seeds[] = {{}};
return sol_invoke_signed(
instruction,
account_infos,
account_infos_len,
signers_seeds,
0
);
}
/**@}*/
/** /**
* Debugging utilities * Debugging utilities
* @{ * @{

View File

@ -1,6 +1,10 @@
//! @brief Solana Native program entry point //! @brief Solana Native program entry point
use crate::{account::KeyedAccount, instruction::InstructionError, pubkey::Pubkey}; use crate::{
account::Account, account::KeyedAccount, instruction::CompiledInstruction,
instruction::InstructionError, message::Message, pubkey::Pubkey,
};
use std::{cell::RefCell, rc::Rc};
// Prototype of a native program entry point // Prototype of a native program entry point
/// ///
@ -23,6 +27,7 @@ pub type LoaderEntrypoint = unsafe extern "C" fn(
program_id: &Pubkey, program_id: &Pubkey,
keyed_accounts: &[KeyedAccount], keyed_accounts: &[KeyedAccount],
instruction_data: &[u8], instruction_data: &[u8],
invoke_context: &dyn InvokeContext,
) -> Result<(), InstructionError>; ) -> Result<(), InstructionError>;
/// Convenience macro to declare a native program /// Convenience macro to declare a native program
@ -134,8 +139,22 @@ macro_rules! declare_loader(
program_id: &$crate::pubkey::Pubkey, program_id: &$crate::pubkey::Pubkey,
keyed_accounts: &[$crate::account::KeyedAccount], keyed_accounts: &[$crate::account::KeyedAccount],
instruction_data: &[u8], instruction_data: &[u8],
invoke_context: &mut dyn $crate::entrypoint_native::InvokeContext,
) -> Result<(), $crate::instruction::InstructionError> { ) -> Result<(), $crate::instruction::InstructionError> {
$entrypoint(program_id, keyed_accounts, instruction_data) $entrypoint(program_id, keyed_accounts, instruction_data, invoke_context)
} }
) )
); );
/// Cross-program invocation context passed to loaders
pub trait InvokeContext {
fn push(&mut self, key: &Pubkey) -> Result<(), InstructionError>;
fn pop(&mut self);
fn verify_and_update(
&mut self,
message: &Message,
instruction: &CompiledInstruction,
signers: &[Pubkey],
accounts: &[Rc<RefCell<Account>>],
) -> Result<(), InstructionError>;
}

View File

@ -87,7 +87,7 @@ pub enum InstructionError {
RentEpochModified, RentEpochModified,
/// The instruction expected additional account keys /// The instruction expected additional account keys
#[error("insufficient account key count for instruction")] #[error("insufficient account keys for instruction")]
NotEnoughAccountKeys, NotEnoughAccountKeys,
/// A non-system program changed the size of the account data /// A non-system program changed the size of the account data
@ -138,6 +138,26 @@ pub enum InstructionError {
/// Unsupported program id /// Unsupported program id
#[error("Unsupported program id")] #[error("Unsupported program id")]
UnsupportedProgramId, UnsupportedProgramId,
/// Writable bit on account info changed, but shouldn't have
#[error("Writable bit on account info changed, but shouldn't have")]
WritableModified,
/// Signer bit on account info changed, but shouldn't have
#[error("Signer bit on account info changed, but shouldn't have")]
SignerModified,
/// Cross-program invocation call depth too deep
#[error("Cross-program invocation call depth too deep")]
CallDepth,
/// An account required by the instruction is missing
#[error("An account required by the instruction is missing")]
MissingAccount,
/// Cross-program invocation reentrancy not allowed for this instruction
#[error("Cross-program invocation reentrancy not allowed for this instruction")]
ReentrancyNotAllowed,
} }
impl InstructionError { impl InstructionError {

View File

@ -61,6 +61,7 @@ pub use solana_sdk_macro::declare_id;
pub mod account_info; pub mod account_info;
pub mod entrypoint; pub mod entrypoint;
pub mod log; pub mod log;
pub mod program;
pub mod program_error; pub mod program_error;
// Modules not usable by on-chain programs // Modules not usable by on-chain programs

42
sdk/src/program.rs Normal file
View File

@ -0,0 +1,42 @@
#![cfg(feature = "program")]
use crate::{
account_info::AccountInfo, entrypoint::ProgramResult, entrypoint::SUCCESS,
instruction::Instruction,
};
/// Invoke a cross-program instruction
pub fn invoke(instruction: &Instruction, account_infos: &[AccountInfo]) -> ProgramResult {
invoke_signed(instruction, account_infos, &[])
}
/// Invoke a cross-program instruction with program signatures
pub fn invoke_signed(
instruction: &Instruction,
account_infos: &[AccountInfo],
signers_seeds: &[&[&str]],
) -> ProgramResult {
let result = unsafe {
sol_invoke_signed_rust(
instruction as *const _ as *const u8,
account_infos as *const _ as *const u8,
account_infos.len() as u64,
signers_seeds as *const _ as *const u8,
signers_seeds.len() as u64,
)
};
match result {
SUCCESS => Ok(()),
_ => Err(result.into()),
}
}
extern "C" {
fn sol_invoke_signed_rust(
instruction_addr: *const u8,
account_infos_addr: *const u8,
account_infos_len: u64,
signers_seeds_addr: *const u8,
signers_seeds_len: u64,
) -> u64;
}