Add Cross-program invocations (#9582)
This commit is contained in:
53
Cargo.lock
generated
53
Cargo.lock
generated
@ -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"
|
||||||
|
20
programs/bpf/Cargo.lock
generated
20
programs/bpf/Cargo.lock
generated
@ -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"
|
||||||
|
@ -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",
|
||||||
|
@ -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(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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",
|
||||||
|
131
programs/bpf/c/src/invoke/invoke.c
Normal file
131
programs/bpf/c/src/invoke/invoke.c
Normal 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, ¶ms, 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;
|
||||||
|
}
|
126
programs/bpf/c/src/invoked/invoked.c
Normal file
126
programs/bpf/c/src/invoked/invoked.c
Normal 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, ¶ms, 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, ¶ms, 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, ¶ms, 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, ¶ms, 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, ¶ms, 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;
|
||||||
|
}
|
27
programs/bpf/rust/invoke/Cargo.toml
Normal file
27
programs/bpf/rust/invoke/Cargo.toml
Normal 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"]
|
2
programs/bpf/rust/invoke/Xargo.toml
Normal file
2
programs/bpf/rust/invoke/Xargo.toml
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
[target.bpfel-unknown-unknown.dependencies.std]
|
||||||
|
features = []
|
145
programs/bpf/rust/invoke/src/lib.rs
Normal file
145
programs/bpf/rust/invoke/src/lib.rs
Normal 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(())
|
||||||
|
}
|
26
programs/bpf/rust/invoked/Cargo.toml
Normal file
26
programs/bpf/rust/invoked/Cargo.toml
Normal 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"]
|
2
programs/bpf/rust/invoked/Xargo.toml
Normal file
2
programs/bpf/rust/invoked/Xargo.toml
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
[target.bpfel-unknown-unknown.dependencies.std]
|
||||||
|
features = []
|
28
programs/bpf/rust/invoked/src/instruction.rs
Normal file
28
programs/bpf/rust/invoked/src/instruction.rs
Normal 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,
|
||||||
|
}
|
||||||
|
}
|
160
programs/bpf/rust/invoked/src/lib.rs
Normal file
160
programs/bpf/rust/invoked/src/lib.rs
Normal 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(())
|
||||||
|
}
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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"
|
||||||
|
@ -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() {
|
||||||
|
@ -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, ¶meter_account));
|
keyed_accounts.push(KeyedAccount::new(&program_key, false, ¶meter_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, ¶meter_account));
|
keyed_accounts.push(KeyedAccount::new(&duplicate_key, false, ¶meter_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()
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
@ -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(¬_owned_key, ¬_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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)?;
|
||||||
|
@ -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
|
||||||
* @{
|
* @{
|
||||||
|
@ -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>;
|
||||||
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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
42
sdk/src/program.rs
Normal 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;
|
||||||
|
}
|
Reference in New Issue
Block a user