diff --git a/.gitignore b/.gitignore index f68c749..2598164 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ /target *.dot .vscode/ +.DS_Store +/*.sol +/*.yul diff --git a/Cargo.lock b/Cargo.lock index 5b89db9..2957c55 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,18 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + [[package]] name = "aho-corasick" version = "1.1.2" @@ -11,6 +23,28 @@ dependencies = [ "memchr", ] +[[package]] +name = "alloy-primitives" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "600d34d8de81e23b6d909c094e23b3d357e01ca36b78a8c5424c501eedbe86f0" +dependencies = [ + "alloy-rlp", + "bytes", + "cfg-if", + "const-hex", + "derive_more", + "hex-literal", + "itoa", + "k256", + "keccak-asm", + "proptest", + "rand", + "ruint", + "serde", + "tiny-keccak", +] + [[package]] name = "alloy-rlp" version = "0.3.4" @@ -21,32 +55,11 @@ dependencies = [ "bytes", ] -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - -[[package]] -name = "android_system_properties" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" -dependencies = [ - "libc", -] - [[package]] name = "anyhow" -version = "1.0.79" +version = "1.0.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" - -[[package]] -name = "arc-swap" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6" +checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1" [[package]] name = "ark-ff" @@ -78,7 +91,7 @@ dependencies = [ "ark-std 0.4.0", "derivative", "digest 0.10.7", - "itertools", + "itertools 0.10.5", "num-bigint", "num-traits", "paste", @@ -178,27 +191,15 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi", - "libc", - "winapi", -] - [[package]] name = "auto_impl" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fee3da8ef1276b0bee5dd1c7258010d8fffd31801447323115a25560e1327b89" +checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42" dependencies = [ - "proc-macro-error", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.52", ] [[package]] @@ -207,6 +208,33 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + [[package]] name = "bitflags" version = "1.3.2" @@ -215,9 +243,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.1" +version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" +checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" [[package]] name = "bitvec" @@ -232,10 +260,22 @@ dependencies = [ ] [[package]] -name = "bumpalo" -version = "3.14.0" +name = "blake2" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] [[package]] name = "byte-slice-cast" @@ -257,12 +297,9 @@ checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" [[package]] name = "cc" -version = "1.0.83" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" -dependencies = [ - "libc", -] +checksum = "a0ba8f7aaa012f30d5b2861462f6708eccd49c3c39863fe083a308035f63d723" [[package]] name = "cfg-if" @@ -270,49 +307,85 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -[[package]] -name = "chrono" -version = "0.4.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" -dependencies = [ - "android-tzdata", - "iana-time-zone", - "js-sys", - "num-traits", - "wasm-bindgen", - "windows-targets 0.48.5", -] - [[package]] name = "clap" -version = "3.2.25" +version = "2.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" +checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" dependencies = [ - "atty", "bitflags 1.3.2", - "clap_lex", - "indexmap 1.9.3", - "strsim", - "termcolor", "textwrap", + "unicode-width", ] [[package]] -name = "clap_lex" -version = "0.2.4" +name = "colored" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" +checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8" dependencies = [ - "os_str_bytes", + "lazy_static", + "windows-sys 0.48.0", ] [[package]] -name = "core-foundation-sys" -version = "0.8.6" +name = "const-hex" +version = "1.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" +checksum = "b37dae8c8ded08d5ec72caa1b4204a5344047cd4a2c7387e3d150020abfbc1c9" +dependencies = [ + "cfg-if", + "cpufeatures", + "hex", + "proptest", + "serde", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +dependencies = [ + "libc", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" [[package]] name = "crunchy" @@ -320,6 +393,18 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core", + "subtle", + "zeroize", +] + [[package]] name = "crypto-common" version = "0.1.6" @@ -331,10 +416,14 @@ dependencies = [ ] [[package]] -name = "delta_inc" -version = "0.3.3" +name = "der" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf2a164be110e755b9c25bed8c307ea847c87e1a34ec02f1893a4914ade978e5" +checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" +dependencies = [ + "const-oid", + "zeroize", +] [[package]] name = "derivative" @@ -348,10 +437,17 @@ dependencies = [ ] [[package]] -name = "destructure_traitobject" -version = "0.2.0" +name = "derive_more" +version = "0.99.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c877555693c14d2f84191cfd3ad8582790fc52b5e2274b40b59cf5f5cea25c7" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version 0.4.0", + "syn 1.0.109", +] [[package]] name = "digest" @@ -368,14 +464,50 @@ version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ + "block-buffer", + "const-oid", "crypto-common", + "subtle", +] + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest 0.10.7", + "elliptic-curve", + "rfc6979", + "signature", + "spki", ] [[package]] name = "either" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" + +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest 0.10.7", + "ff", + "generic-array", + "group", + "pkcs8", + "rand_core", + "sec1", + "subtle", + "zeroize", +] [[package]] name = "equivalent" @@ -383,6 +515,41 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "era-compiler-common" +version = "1.5.0" +source = "git+https://github.com/matter-labs/era-compiler-common?branch=main#6781681145a37d9809057cf6fc3fa667abd545af" +dependencies = [ + "anyhow", + "serde", + "serde_json", + "serde_stacker", +] + +[[package]] +name = "era-compiler-llvm-context" +version = "1.4.1" +dependencies = [ + "anyhow", + "era-compiler-common", + "hex", + "inkwell", + "itertools 0.12.1", + "md5", + "num", + "once_cell", + "pallet-contracts-pvm-llapi", + "regex", + "revive-builtins", + "revive-linker", + "revive-stdlib", + "semver 1.0.22", + "serde", + "sha2", + "sha3", + "zkevm_opcode_defs", +] + [[package]] name = "errno" version = "0.3.8" @@ -390,20 +557,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] -name = "evmil" -version = "0.4.5" +name = "ethbloom" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa3fb41a5c2b4a373a644215b948d596060eddcddb7c8ae871dc1e4e9bc7b13a" +checksum = "c22d4b5885b6aa2fe5e8b9329fb8d232bf739e434e6b87347c63bdd00c120f60" dependencies = [ - "clap", - "delta_inc", - "log", - "log4rs", - "ruint", + "crunchy", + "fixed-hash", + "impl-rlp", + "impl-serde", + "tiny-keccak", +] + +[[package]] +name = "ethereum-types" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02d215cbf040552efcbe99a38372fe80ab9d00268e20012b79fcd0f073edd8ee" +dependencies = [ + "ethbloom", + "fixed-hash", + "impl-rlp", + "impl-serde", + "primitive-types", + "uint", ] [[package]] @@ -429,6 +610,16 @@ dependencies = [ "bytes", ] +[[package]] +name = "ff" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "rand_core", + "subtle", +] + [[package]] name = "fixed-hash" version = "0.8.0" @@ -441,12 +632,6 @@ dependencies = [ "static_assertions", ] -[[package]] -name = "fixedbitset" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" - [[package]] name = "fnv" version = "1.0.7" @@ -467,6 +652,7 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] @@ -491,10 +677,24 @@ dependencies = [ ] [[package]] -name = "hashbrown" -version = "0.12.3" +name = "group" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core", + "subtle", +] + +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash", +] [[package]] name = "hashbrown" @@ -503,12 +703,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" [[package]] -name = "hermit-abi" -version = "0.1.19" +name = "heck" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" dependencies = [ - "libc", + "unicode-segmentation", ] [[package]] @@ -518,32 +718,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] -name = "humantime" -version = "2.1.0" +name = "hex-literal" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" +checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" [[package]] -name = "iana-time-zone" -version = "0.1.59" +name = "hmac" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6a67363e2aa4443928ce15e57ebae94fd8949958fd1223c4cfc0cd473ad7539" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "wasm-bindgen", - "windows-core", + "digest 0.10.7", ] [[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" +name = "home" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" dependencies = [ - "cc", + "windows-sys 0.52.0", ] [[package]] @@ -555,6 +750,24 @@ dependencies = [ "parity-scale-codec", ] +[[package]] +name = "impl-rlp" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28220f89297a075ddc7245cd538076ee98b01f2a9c23a53a4f1105d5a322808" +dependencies = [ + "rlp", +] + +[[package]] +name = "impl-serde" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc88fc67028ae3db0c853baa36269d398d5f45b6982f95549ff5def78c935cd" +dependencies = [ + "serde", +] + [[package]] name = "impl-trait-for-tuples" version = "0.2.2" @@ -568,19 +781,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "1.9.3" +version = "2.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", -] - -[[package]] -name = "indexmap" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" +checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4" dependencies = [ "equivalent", "hashbrown 0.14.3", @@ -588,27 +791,24 @@ dependencies = [ [[package]] name = "inkwell" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f4fcb4a4fa0b8f7b4178e24e6317d6f8b95ab500d8e6e1bd4283b6860e369c1" +version = "0.4.0" dependencies = [ "either", "inkwell_internals", "libc", "llvm-sys", "once_cell", - "parking_lot", + "serde", + "thiserror", ] [[package]] name = "inkwell_internals" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b185e7d068d6820411502efa14d8fbf010750485399402156b72dd2a548ef8e9" +version = "0.9.0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.52", ] [[package]] @@ -620,6 +820,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.10" @@ -627,12 +836,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" [[package]] -name = "js-sys" -version = "0.3.66" +name = "k256" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca" +checksum = "956ff9b67e26e1a6a866cb758f12c6f8746208489e3e4a4b5580802f2f0a587b" dependencies = [ - "wasm-bindgen", + "cfg-if", + "ecdsa", + "elliptic-curve", + "once_cell", + "sha2", + "signature", +] + +[[package]] +name = "keccak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "keccak-asm" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb8515fff80ed850aea4a1595f2e519c003e2a00a82fe168ebf5269196caf444" +dependencies = [ + "digest 0.10.7", + "sha3-asm", ] [[package]] @@ -643,9 +876,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.152" +version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" [[package]] name = "libm" @@ -654,16 +887,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" [[package]] -name = "linked-hash-map" -version = "0.5.6" +name = "libmimalloc-sys" +version = "0.1.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" +checksum = "3979b5c37ece694f1f5e51e7ecc871fdb0f517ed04ee45f88d15d6d553cb9664" +dependencies = [ + "cc", + "libc", +] [[package]] name = "linux-raw-sys" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" [[package]] name = "lld-sys" @@ -683,59 +920,20 @@ dependencies = [ "lazy_static", "libc", "regex", - "semver 1.0.21", -] - -[[package]] -name = "lock_api" -version = "0.4.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" -dependencies = [ - "autocfg", - "scopeguard", + "semver 1.0.22", ] [[package]] name = "log" -version = "0.4.20" +version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" -dependencies = [ - "serde", -] +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" [[package]] -name = "log-mdc" -version = "0.1.0" +name = "md5" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a94d21414c1f4a51209ad204c1776a3d0765002c76c6abcb602a6f09f1e881c7" - -[[package]] -name = "log4rs" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d36ca1786d9e79b8193a68d480a0907b612f109537115c6ff655a3a1967533fd" -dependencies = [ - "anyhow", - "arc-swap", - "chrono", - "derivative", - "fnv", - "humantime", - "libc", - "log", - "log-mdc", - "parking_lot", - "serde", - "serde-value", - "serde_json", - "serde_yaml", - "thiserror", - "thread-id", - "typemap-ors", - "winapi", -] +checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" [[package]] name = "memchr" @@ -743,6 +941,29 @@ version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" +[[package]] +name = "mimalloc" +version = "0.1.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa01922b5ea280a911e323e4d2fd24b7fe5cc4042e0d2cda3c40775cdc4bdc9c" +dependencies = [ + "libmimalloc-sys", +] + +[[package]] +name = "num" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05180d69e3da0e530ba2a1dae5110317e49e3b7f3d41be227dc5f92e49ee7af" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + [[package]] name = "num-bigint" version = "0.4.4" @@ -755,20 +976,51 @@ dependencies = [ ] [[package]] -name = "num-integer" -version = "0.1.45" +name = "num-complex" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +checksum = "23c6602fda94a57c990fe0df199a035d83576b496aa29f4e634a8ac6004e68a6" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d869c01cc0c455284163fd0092f1f93835385ccab5a98a0dcc497b2f8bf055a9" dependencies = [ "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +dependencies = [ + "autocfg", + "num-bigint", + "num-integer", "num-traits", ] [[package]] name = "num-traits" -version = "0.2.17" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" dependencies = [ "autocfg", "libm", @@ -790,20 +1042,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] -name = "ordered-float" -version = "2.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c" +name = "pallet-contracts-pvm-llapi" +version = "0.1.0" dependencies = [ - "num-traits", + "inkwell", ] -[[package]] -name = "os_str_bytes" -version = "6.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" - [[package]] name = "parity-scale-codec" version = "3.6.9" @@ -830,29 +1074,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "parking_lot" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-targets 0.48.5", -] - [[package]] name = "paste" version = "1.0.14" @@ -860,10 +1081,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" [[package]] -name = "pest" -version = "2.7.6" +name = "path-slash" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f200d8d83c44a45b21764d1916299752ca035d15ecd46faca3e9a2a2bf6ad06" +checksum = "1e91099d4268b0e11973f036e885d652fb0b21fedcf69738c627f94db6a44f42" + +[[package]] +name = "pest" +version = "2.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f8023d0fb78c8e03784ea1c7f3fa36e68a723138990b8d5a47d916b651e7a8" dependencies = [ "memchr", "thiserror", @@ -871,33 +1098,62 @@ dependencies = [ ] [[package]] -name = "petgraph" -version = "0.6.4" +name = "pkcs8" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" dependencies = [ - "fixedbitset", - "indexmap 2.1.0", + "der", + "spki", +] + +[[package]] +name = "polkavm" +version = "0.9.3" +source = "git+https://github.com/koute/polkavm.git#658b2a612e966c5dde0860e7d95d57ffe87a0fdf" +dependencies = [ + "libc", + "log", + "polkavm-assembler", + "polkavm-common", + "polkavm-linux-raw", +] + +[[package]] +name = "polkavm-assembler" +version = "0.9.0" +source = "git+https://github.com/koute/polkavm.git#658b2a612e966c5dde0860e7d95d57ffe87a0fdf" +dependencies = [ + "log", ] [[package]] name = "polkavm-common" -version = "0.3.0" -source = "git+https://github.com/koute/polkavm.git?rev=3552524a248a025de8e608394fcf9eb7c528eb11#3552524a248a025de8e608394fcf9eb7c528eb11" +version = "0.9.0" +source = "git+https://github.com/koute/polkavm.git#658b2a612e966c5dde0860e7d95d57ffe87a0fdf" +dependencies = [ + "log", +] [[package]] name = "polkavm-linker" -version = "0.3.0" -source = "git+https://github.com/koute/polkavm.git?rev=3552524a248a025de8e608394fcf9eb7c528eb11#3552524a248a025de8e608394fcf9eb7c528eb11" +version = "0.9.2" +source = "git+https://github.com/koute/polkavm.git#658b2a612e966c5dde0860e7d95d57ffe87a0fdf" dependencies = [ "gimli", "hashbrown 0.14.3", "log", "object", "polkavm-common", + "regalloc2", "rustc-demangle", ] +[[package]] +name = "polkavm-linux-raw" +version = "0.9.0" +source = "git+https://github.com/koute/polkavm.git#658b2a612e966c5dde0860e7d95d57ffe87a0fdf" + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -912,14 +1168,16 @@ checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2" dependencies = [ "fixed-hash", "impl-codec", + "impl-rlp", + "impl-serde", "uint", ] [[package]] name = "proc-macro-crate" -version = "2.0.1" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97dc5fea232fc28d2f597b37c4876b348a40e33f3b02cc975c8d006d78d94b1a" +checksum = "b00f26d3400549137f92511a46ac1cd8ce37cb5598a96d382381458b992a5d24" dependencies = [ "toml_datetime", "toml_edit", @@ -951,9 +1209,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.76" +version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" dependencies = [ "unicode-ident", ] @@ -964,16 +1222,35 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "31b476131c3c86cb68032fdc5cb6d5a1045e3e42d96b69fa599fd77701e1f5bf" dependencies = [ - "bitflags 2.4.1", + "bit-set", + "bit-vec", + "bitflags 2.4.2", "lazy_static", "num-traits", "rand", "rand_chacha", "rand_xorshift", "regex-syntax", + "rusty-fork", + "tempfile", "unarray", ] +[[package]] +name = "psm" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5787f7cda34e3033a72192c018bc5883100330f362ef279a8cbccfce8bb4e874" +dependencies = [ + "cc", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + [[package]] name = "quote" version = "1.0.35" @@ -1029,19 +1306,43 @@ dependencies = [ ] [[package]] -name = "redox_syscall" -version = "0.4.1" +name = "rayon" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +checksum = "e4963ed1bc86e4f3ee217022bd855b297cef07fb9eac5dfa1f788b220b49b3bd" dependencies = [ - "bitflags 1.3.2", + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "regalloc2" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad156d539c879b7a24a363a2016d77961786e71f48f2e2fc8302a92abd2429a6" +dependencies = [ + "hashbrown 0.13.2", + "log", + "rustc-hash", + "slice-group-by", + "smallvec", ] [[package]] name = "regex" -version = "1.10.2" +version = "1.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" dependencies = [ "aho-corasick", "memchr", @@ -1051,9 +1352,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.3" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" dependencies = [ "aho-corasick", "memchr", @@ -1071,58 +1372,75 @@ name = "revive-builtins" version = "0.1.0" [[package]] -name = "revive-cli" +name = "revive-integration" version = "0.1.0" dependencies = [ - "evmil", + "alloy-primitives", + "era-compiler-llvm-context", "hex", - "revive-codegen", - "revive-ir", - "revive-target-polkavm", + "parity-scale-codec", + "polkavm", + "revive-solidity", ] [[package]] -name = "revive-codegen" -version = "0.1.0" -dependencies = [ - "inkwell", - "revive-compilation-target", - "revive-ir", -] - -[[package]] -name = "revive-compilation-target" -version = "0.1.0" -dependencies = [ - "inkwell", -] - -[[package]] -name = "revive-ir" -version = "0.1.0" -dependencies = [ - "evmil", - "indexmap 2.1.0", - "petgraph", - "primitive-types", - "revive-compilation-target", -] - -[[package]] -name = "revive-target-polkavm" +name = "revive-linker" version = "0.1.0" dependencies = [ + "anyhow", "inkwell", "libc", "lld-sys", "polkavm-common", "polkavm-linker", "revive-builtins", - "revive-codegen", - "revive-compilation-target", "tempfile", ] +[[package]] +name = "revive-solidity" +version = "1.4.0" +dependencies = [ + "anyhow", + "colored", + "era-compiler-common", + "era-compiler-llvm-context", + "hex", + "inkwell", + "md5", + "mimalloc", + "num", + "once_cell", + "path-slash", + "rand", + "rayon", + "regex", + "semver 1.0.22", + "serde", + "serde_json", + "sha3", + "structopt", + "thiserror", + "which", +] + +[[package]] +name = "revive-stdlib" +version = "0.1.0" +dependencies = [ + "inkwell", +] + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + [[package]] name = "rlp" version = "0.5.2" @@ -1135,9 +1453,9 @@ dependencies = [ [[package]] name = "ruint" -version = "1.11.1" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608a5726529f2f0ef81b8fde9873c4bb829d6b5b5ca6be4d97345ddf0749c825" +checksum = "49b1d9521f889713d1221270fdd63370feca7e5c71a18745343402fa86e4f04f" dependencies = [ "alloy-rlp", "ark-ff 0.3.0", @@ -1159,9 +1477,9 @@ dependencies = [ [[package]] name = "ruint-macro" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e666a5496a0b2186dbcd0ff6106e29e093c15591bde62c20d3842007c6978a09" +checksum = "f86854cf50259291520509879a5c294c3c9a4c334e9ff65071c51e42ef1e2343" [[package]] name = "rustc-demangle" @@ -1169,6 +1487,12 @@ version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustc-hex" version = "2.1.0" @@ -1190,33 +1514,53 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver 1.0.21", + "semver 1.0.22", ] [[package]] name = "rustix" -version = "0.38.28" +version = "0.38.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" +checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.4.2", "errno", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "rusty-fork" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", ] [[package]] name = "ryu" -version = "1.0.16" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" +checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" [[package]] -name = "scopeguard" -version = "1.2.0" +name = "sec1" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] [[package]] name = "semver" @@ -1229,9 +1573,12 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.21" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0" +checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" +dependencies = [ + "serde", +] [[package]] name = "semver-parser" @@ -1244,39 +1591,29 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.195" +version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02" +checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" dependencies = [ "serde_derive", ] -[[package]] -name = "serde-value" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" -dependencies = [ - "ordered-float", - "serde", -] - [[package]] name = "serde_derive" -version = "1.0.195" +version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c" +checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.52", ] [[package]] name = "serde_json" -version = "1.0.111" +version = "1.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "176e46fa42316f18edd598015a5166857fc835ec732f5215eac6b7bdbf0a84f4" +checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" dependencies = [ "itoa", "ryu", @@ -1284,22 +1621,77 @@ dependencies = [ ] [[package]] -name = "serde_yaml" -version = "0.8.26" +name = "serde_stacker" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578a7433b776b56a35785ed5ce9a7e777ac0598aac5a6dd1b4b18a307c7fc71b" +checksum = "babfccff5773ff80657f0ecf553c7c516bdc2eb16389c0918b36b73e7015276e" dependencies = [ - "indexmap 1.9.3", - "ryu", "serde", - "yaml-rust", + "stacker", ] [[package]] -name = "smallvec" -version = "1.11.2" +name = "sha2" +version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.7", +] + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest 0.10.7", + "keccak", +] + +[[package]] +name = "sha3-asm" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bac61da6b35ad76b195eb4771210f947734321a8d81d7738e1580d953bc7a15e" +dependencies = [ + "cc", + "cfg-if", +] + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest 0.10.7", + "rand_core", +] + +[[package]] +name = "slice-group-by" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "826167069c09b99d56f31e9ae5c99049e932a98c9dc2dac47645b08dbbf76ba7" + +[[package]] +name = "smallvec" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] [[package]] name = "stable_deref_trait" @@ -1307,6 +1699,19 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "stacker" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c886bd4480155fd3ef527d45e9ac8dd7118a898a46530b7b94c3e21866259fce" +dependencies = [ + "cc", + "cfg-if", + "libc", + "psm", + "winapi", +] + [[package]] name = "static_assertions" version = "1.1.0" @@ -1314,10 +1719,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] -name = "strsim" -version = "0.10.0" +name = "structopt" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +checksum = "0c6b5c64445ba8094a6ab0c3cd2ad323e07171012d9c98b0b15651daf1787a10" +dependencies = [ + "clap", + "lazy_static", + "structopt-derive", +] + +[[package]] +name = "structopt-derive" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" [[package]] name = "syn" @@ -1332,9 +1761,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.48" +version = "2.0.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07" dependencies = [ "proc-macro2", "quote", @@ -1349,60 +1778,52 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tempfile" -version = "3.9.0" +version = "3.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" dependencies = [ "cfg-if", "fastrand", - "redox_syscall", "rustix", - "windows-sys", -] - -[[package]] -name = "termcolor" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" -dependencies = [ - "winapi-util", + "windows-sys 0.52.0", ] [[package]] name = "textwrap" -version = "0.16.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] [[package]] name = "thiserror" -version = "1.0.56" +version = "1.0.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" +checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.56" +version = "1.0.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" +checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.52", ] [[package]] -name = "thread-id" -version = "4.2.1" +name = "tiny-keccak" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0ec81c46e9eb50deaa257be2f148adf052d1fb7701cfd55ccfab2525280b70b" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" dependencies = [ - "libc", - "winapi", + "crunchy", ] [[package]] @@ -1417,20 +1838,11 @@ version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" dependencies = [ - "indexmap 2.1.0", + "indexmap", "toml_datetime", "winnow", ] -[[package]] -name = "typemap-ors" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a68c24b707f02dd18f1e4ccceb9d49f2058c2fb86384ef9972592904d7a28867" -dependencies = [ - "unsafe-any-ors", -] - [[package]] name = "typenum" version = "1.17.0" @@ -1468,13 +1880,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] -name = "unsafe-any-ors" -version = "1.0.0" +name = "unicode-segmentation" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a303d30665362d9680d7d91d78b23f5f899504d4f08b3c4cf08d055d87c0ad" -dependencies = [ - "destructure_traitobject", -] +checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" + +[[package]] +name = "unicode-width" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" [[package]] name = "valuable" @@ -1488,6 +1903,15 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "wait-timeout" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +dependencies = [ + "libc", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -1495,59 +1919,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] -name = "wasm-bindgen" -version = "0.2.89" +name = "which" +version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e" +checksum = "9bf3ea8596f3a0dd5980b46430f2058dfe2c36a27ccfbb1845d6fbfcd9ba6e14" dependencies = [ - "cfg-if", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.89" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826" -dependencies = [ - "bumpalo", - "log", + "either", + "home", "once_cell", - "proc-macro2", - "quote", - "syn 2.0.48", - "wasm-bindgen-shared", + "rustix", + "windows-sys 0.48.0", ] -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.89" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.89" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.48", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.89" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" - [[package]] name = "winapi" version = "0.3.9" @@ -1564,15 +1947,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" -[[package]] -name = "winapi-util" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" -dependencies = [ - "winapi", -] - [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -1580,12 +1954,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] -name = "windows-core" -version = "0.52.0" +name = "windows-sys" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets 0.52.0", + "windows-targets 0.48.5", ] [[package]] @@ -1594,7 +1968,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.0", + "windows-targets 0.52.4", ] [[package]] @@ -1614,17 +1988,17 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" dependencies = [ - "windows_aarch64_gnullvm 0.52.0", - "windows_aarch64_msvc 0.52.0", - "windows_i686_gnu 0.52.0", - "windows_i686_msvc 0.52.0", - "windows_x86_64_gnu 0.52.0", - "windows_x86_64_gnullvm 0.52.0", - "windows_x86_64_msvc 0.52.0", + "windows_aarch64_gnullvm 0.52.4", + "windows_aarch64_msvc 0.52.4", + "windows_i686_gnu 0.52.4", + "windows_i686_msvc 0.52.4", + "windows_x86_64_gnu 0.52.4", + "windows_x86_64_gnullvm 0.52.4", + "windows_x86_64_msvc 0.52.4", ] [[package]] @@ -1635,9 +2009,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" +checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" [[package]] name = "windows_aarch64_msvc" @@ -1647,9 +2021,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" +checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" [[package]] name = "windows_i686_gnu" @@ -1659,9 +2033,9 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" +checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" [[package]] name = "windows_i686_msvc" @@ -1671,9 +2045,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" +checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" [[package]] name = "windows_x86_64_gnu" @@ -1683,9 +2057,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" +checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" [[package]] name = "windows_x86_64_gnullvm" @@ -1695,9 +2069,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" +checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" [[package]] name = "windows_x86_64_msvc" @@ -1707,15 +2081,15 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" +checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" [[package]] name = "winnow" -version = "0.5.33" +version = "0.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7520bbdec7211caa7c4e682eb1fbe07abe20cee6756b6e00f537c82c11816aa" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" dependencies = [ "memchr", ] @@ -1730,12 +2104,23 @@ dependencies = [ ] [[package]] -name = "yaml-rust" -version = "0.4.5" +name = "zerocopy" +version = "0.7.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" dependencies = [ - "linked-hash-map", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.52", ] [[package]] @@ -1755,5 +2140,19 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.52", +] + +[[package]] +name = "zkevm_opcode_defs" +version = "1.4.1" +source = "git+https://github.com/matter-labs/era-zkevm_opcode_defs?branch=v1.4.1#ba8228ff0582d21f64d6a319d50d0aec48e9e7b6" +dependencies = [ + "bitflags 2.4.2", + "blake2", + "ethereum-types", + "k256", + "lazy_static", + "sha2", + "sha3", ] diff --git a/Cargo.toml b/Cargo.toml index aa3128c..4ebe0fd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,14 +3,33 @@ resolver = "2" members = ["crates/*"] [workspace.dependencies] -evmil = "0.4" hex = "0.4" petgraph = "0.6" -primitive-types = "0.12" -indexmap = "2.1.0" -inkwell = { version = "0.2.0", features = ["target-riscv", "no-libffi-linking", "llvm16-0"] } cc = "1.0" libc = "0.2" tempfile = "3.8" -polkavm-common = { git = "https://github.com/koute/polkavm.git", rev = "3552524a248a025de8e608394fcf9eb7c528eb11" } -polkavm-linker = { git = "https://github.com/koute/polkavm.git", rev = "3552524a248a025de8e608394fcf9eb7c528eb11" } +#inkwell = { version = "0.4.0", default-features = false, features = ["llvm16-0", "no-libffi-linking", "target-riscv"] } +inkwell = { path = "../inkwell", default-features = false, features = ["serde", "llvm16-0", "no-libffi-linking", "target-riscv"] } +anyhow = "1.0" +semver = { version = "1.0", features = [ "serde" ] } +itertools = "0.12" +serde = { version = "1.0", features = [ "derive" ] } +serde_json = { version = "1.0", features = [ "arbitrary_precision" ] } +regex = "1.10" +once_cell = "1.19" +num = "0.4" +sha2 = "0.10" +sha3 = "0.10" +md5 = "0.7" +colored = "2.1" +thiserror = "1.0" +which = "5.0" +path-slash = "0.2" +rayon = "1.8" +structopt = { version = "0.3", default-features = false } +rand = "0.8" +polkavm-common = { git = "https://github.com/koute/polkavm.git" } +polkavm-linker = { git = "https://github.com/koute/polkavm.git" } +polkavm = { git = "https://github.com/koute/polkavm.git" } +parity-scale-codec = "3.6" +alloy-primitives = "0.6" diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 0000000..9fc9f4c --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,191 @@ + + Apache License + Version 2.0, January 2004 + https://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + Copyright (c) 2019 Matter Labs + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 0000000..2739ea6 --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Matter Labs + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..4c8afb8 --- /dev/null +++ b/README.md @@ -0,0 +1,32 @@ +# revive + +YUL and EVM bytecode recompiler to LLVM, targetting RISC-V on PolkaVM. + +Code bases of [frontend](https://github.com/matter-labs/era-compiler-solidity) and [code generator](https://github.com/matter-labs/era-compiler-llvm-context) are forked adapted from ZKSync `zksolc`. + +Primary goal of this codebase currently is to allow for benchmarks comparing runtime performance against ink!, solang and EVM interpreters. + +# TODO + +The project is in a very early PoC phase; at this stage don't expect the produced code to be working nor to be correct for anything more than a basic flipper contract yet. + +- [ ] Efficient implementations of byte swaps, memset, memmove and the like +- [ ] Use drink! for integration tests once we have 64bit support in PolkaVM +- [ ] Exercice `schlau` and possibly `smart-bench` benchmark cases +- [ ] Tests currently rely on the binary being in $PATH, which is very annoying and requires `cargo install` all the times +- [ ] Define how to do deployments +- [ ] Calling conventions for calling other contracts +- [ ] Runtime environment isn't fully figured out; implement all EVM builtins +- [ ] Iron out many leftovers from the ZKVM target + - [ ] Use of exceptions + - [ ] Change long calls (contract calls) + - [ ] Check all alignments, attributes etc. if they still make sense with our target +- [ ] Add a lot more test cases +- [ ] Debug information +- [ ] Look for and implement further optimizations +- [ ] Differential testing against EVM +- [ ] Switch to LLVM 18 which has RV{32,64}E upstream +- [ ] Minimize scope of "stdlib", favorably implement it in high level language instead of LLVM IR. +- [ ] Document differences from EVM +- [ ] Audit for bugs and correctness +- [ ] Rebranding diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml deleted file mode 100644 index efe31d6..0000000 --- a/crates/cli/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -name = "revive-cli" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -hex = { workspace = true } -evmil = { workspace = true } - -revive-ir = { path = "../ir" } -revive-codegen = { path = "../codegen" } -revive-target-polkavm = { path = "../target-polkavm" } \ No newline at end of file diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs deleted file mode 100644 index 7322864..0000000 --- a/crates/cli/src/main.rs +++ /dev/null @@ -1,21 +0,0 @@ -use evmil::bytecode::Disassemble; -use revive_ir::cfg::BasicBlockFormatOption; -use revive_target_polkavm::PolkaVm; - -fn main() { - let hexcode = std::fs::read_to_string(std::env::args().nth(1).unwrap()).unwrap(); - let bytecode = hex::decode(hexcode.trim()).unwrap(); - let instructions = bytecode.disassemble(); - - let mut ir = revive_ir::cfg::Program::new(&instructions); - ir.optimize(); - ir.dot(BasicBlockFormatOption::Ir); - - let target = PolkaVm::default(); - let program = revive_codegen::program::Program::new(&target).unwrap(); - program.emit(ir); - - let artifact = program.compile_and_link(); - - std::fs::write("/tmp/out.pvm", artifact).unwrap(); -} diff --git a/crates/codegen/Cargo.toml b/crates/codegen/Cargo.toml deleted file mode 100644 index c42f91f..0000000 --- a/crates/codegen/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -name = "revive-codegen" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -inkwell = { workspace = true } - -revive-compilation-target = { path = "../compilation-target" } -revive-ir = { path = "../ir" } \ No newline at end of file diff --git a/crates/codegen/src/lib.rs b/crates/codegen/src/lib.rs deleted file mode 100644 index 29146a4..0000000 --- a/crates/codegen/src/lib.rs +++ /dev/null @@ -1,2 +0,0 @@ -mod module; -pub mod program; diff --git a/crates/codegen/src/module.rs b/crates/codegen/src/module.rs deleted file mode 100644 index f9a2460..0000000 --- a/crates/codegen/src/module.rs +++ /dev/null @@ -1,37 +0,0 @@ -use inkwell::{ - module::Module, - support::LLVMString, - targets::{RelocMode, TargetTriple}, -}; -use revive_compilation_target::target::Target; - -pub(crate) fn create<'ctx, T>(target: &'ctx T) -> Result, LLVMString> -where - T: Target<'ctx>, -{ - let module = target.context().create_module("contract"); - - module.set_triple(&TargetTriple::create(::TARGET_TRIPLE)); - module.set_source_file_name("contract.bin"); - - set_flags(target, &module); - - for lib in target.libraries() { - module.link_in_module(lib)?; - } - - Ok(module) -} - -fn set_flags<'ctx, T>(target: &'ctx T, module: &Module<'ctx>) -where - T: Target<'ctx>, -{ - if let RelocMode::PIC = ::RELOC_MODE { - module.add_basic_value_flag( - "PIE Level", - inkwell::module::FlagBehavior::Override, - target.context().i32_type().const_int(2, false), - ); - } -} diff --git a/crates/codegen/src/program.rs b/crates/codegen/src/program.rs deleted file mode 100644 index 5574545..0000000 --- a/crates/codegen/src/program.rs +++ /dev/null @@ -1,93 +0,0 @@ -use inkwell::{ - builder::Builder, - module::{Linkage, Module}, - support::LLVMString, - targets::{FileType, TargetTriple}, - values::{FunctionValue, GlobalValue}, - AddressSpace, -}; - -use revive_compilation_target::environment::Environment; -use revive_compilation_target::target::Target; - -use crate::module; - -pub struct Program<'ctx, T> { - pub module: Module<'ctx>, - pub builder: Builder<'ctx>, - pub calldata: GlobalValue<'ctx>, - pub returndata: GlobalValue<'ctx>, - pub target: &'ctx T, - pub start: FunctionValue<'ctx>, -} - -impl<'ctx, T> Program<'ctx, T> -where - T: Target<'ctx> + Environment<'ctx>, -{ - pub fn new(target: &'ctx T) -> Result { - T::initialize_llvm(); - - let context = target.context(); - - let module = module::create(target)?; - let builder = context.create_builder(); - let address_space = Some(AddressSpace::default()); - - let calldata_type = context.i8_type().array_type(T::CALLDATA_SIZE); - let calldata = module.add_global(calldata_type, address_space, "calldata"); - - let returndata_type = context.i8_type().array_type(T::RETURNDATA_SIZE); - let returndata = module.add_global(returndata_type, address_space, "returndata"); - - let start_fn_type = target.context().void_type().fn_type(&[], false); - let start = module.add_function("start", start_fn_type, Some(Linkage::Internal)); - - Ok(Self { - module, - builder, - calldata, - returndata, - target, - start, - }) - } - - pub fn emit(&self, program: revive_ir::cfg::Program) { - self.emit_start(); - } - - pub fn compile_and_link(&self) -> Vec { - inkwell::targets::Target::from_name(T::TARGET_NAME) - .expect("target name should be valid") - .create_target_machine( - &TargetTriple::create(T::TARGET_TRIPLE), - T::CPU, - T::TARGET_FEATURES, - self.target.optimization_level(), - T::RELOC_MODE, - T::CODE_MODEL, - ) - .expect("target configuration should be valid") - .write_to_memory_buffer(&self.module, FileType::Object) - .map(|out| self.target.link(out.as_slice())) - .expect("linker should succeed") - .to_vec() - } - - fn emit_start(&self) { - let start = self.start; - let block = self - .start - .get_last_basic_block() - .unwrap_or_else(|| self.target.context().append_basic_block(start, "entry")); - - self.builder.position_at_end(block); - self.builder.build_return(None); - - let env_start = self.target.call_start(&self.builder, self.start); - self.module - .link_in_module(env_start) - .expect("entrypoint module should be linkable"); - } -} diff --git a/crates/compilation-target/src/environment.rs b/crates/compilation-target/src/environment.rs deleted file mode 100644 index aa405b7..0000000 --- a/crates/compilation-target/src/environment.rs +++ /dev/null @@ -1,15 +0,0 @@ -use inkwell::{builder::Builder, module::Module, values::FunctionValue}; - -/// [Environment] describes EVM runtime functionality. -pub trait Environment<'ctx> { - const STACK_SIZE: u32 = 1024 * 32; - const CALLDATA_SIZE: u32 = 0x10000; - const RETURNDATA_SIZE: u32 = 0x10000; - const MEMORY_SIZE: u32 = 0x100000; - - /// Build a module containing all required runtime exports and imports. - /// - /// The `start` function is the entrypoint to the contract logic. - /// The returned `Module` is expected to call `start` somewhere. - fn call_start(&'ctx self, builder: &Builder<'ctx>, start: FunctionValue<'ctx>) -> Module<'ctx>; -} diff --git a/crates/compilation-target/src/lib.rs b/crates/compilation-target/src/lib.rs deleted file mode 100644 index d313b7d..0000000 --- a/crates/compilation-target/src/lib.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod environment; -pub mod target; diff --git a/crates/compilation-target/src/target.rs b/crates/compilation-target/src/target.rs deleted file mode 100644 index 69b4414..0000000 --- a/crates/compilation-target/src/target.rs +++ /dev/null @@ -1,27 +0,0 @@ -use inkwell::{ - context::Context, - module::Module, - targets::{CodeModel, RelocMode}, - OptimizationLevel, -}; - -pub trait Target<'ctx> { - const TARGET_NAME: &'ctx str; - const TARGET_TRIPLE: &'ctx str; - const TARGET_FEATURES: &'ctx str; - const CPU: &'ctx str; - const RELOC_MODE: RelocMode = RelocMode::Default; - const CODE_MODEL: CodeModel = CodeModel::Default; - - fn initialize_llvm() { - inkwell::targets::Target::initialize_riscv(&Default::default()); - } - - fn context(&self) -> &Context; - - fn libraries(&'ctx self) -> Vec>; - - fn link(&self, blob: &[u8]) -> Vec; - - fn optimization_level(&self) -> OptimizationLevel; -} diff --git a/crates/integration/Cargo.toml b/crates/integration/Cargo.toml new file mode 100644 index 0000000..5396bd4 --- /dev/null +++ b/crates/integration/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "revive-integration" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +hex = { workspace = true } +polkavm = { workspace = true } +parity-scale-codec = { workspace = true } +revive-solidity = { path = "../solidity" } +era-compiler-llvm-context = { path = "../llvm-context" } +alloy-primitives = { workspace = true } \ No newline at end of file diff --git a/crates/integration/contracts/Computation.sol b/crates/integration/contracts/Computation.sol new file mode 100644 index 0000000..04baeba --- /dev/null +++ b/crates/integration/contracts/Computation.sol @@ -0,0 +1,19 @@ +contract Computation { + function triangle_number(int64 n) public pure returns (int64 sum) { + unchecked { + for (int64 x = 1; x <= n; x++) { + sum += x; + } + } + } + + function odd_product(int32 n) public pure returns (int64) { + unchecked { + int64 prod = 1; + for (int32 x = 1; x <= n; x++) { + prod *= 2 * int64(x) - 1; + } + return prod; + } + } +} diff --git a/crates/integration/contracts/ERC20.sol b/crates/integration/contracts/ERC20.sol new file mode 100644 index 0000000..e1d467b --- /dev/null +++ b/crates/integration/contracts/ERC20.sol @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.0.0/contracts/token/ERC20/IERC20.sol +interface IERC20 { + function totalSupply() external view returns (uint); + + function balanceOf(address account) external view returns (uint); + + function transfer(address recipient, uint amount) external returns (bool); + + function allowance(address owner, address spender) external view returns (uint); + + function approve(address spender, uint amount) external returns (bool); + + function transferFrom( + address sender, + address recipient, + uint amount + ) external returns (bool); + + event Transfer(address indexed from, address indexed to, uint value); + event Approval(address indexed owner, address indexed spender, uint value); +} + + + + +contract ERC20 is IERC20 { + uint public totalSupply; + mapping(address => uint) public balanceOf; + mapping(address => mapping(address => uint)) public allowance; + string public name = "Solidity by Example"; + string public symbol = "SOLBYEX"; + uint8 public decimals = 18; + + function transfer(address recipient, uint amount) external returns (bool) { + balanceOf[msg.sender] -= amount; + balanceOf[recipient] += amount; + emit Transfer(msg.sender, recipient, amount); + return true; + } + + function approve(address spender, uint amount) external returns (bool) { + allowance[msg.sender][spender] = amount; + emit Approval(msg.sender, spender, amount); + return true; + } + + function transferFrom( + address sender, + address recipient, + uint amount + ) external returns (bool) { + allowance[sender][msg.sender] -= amount; + balanceOf[sender] -= amount; + balanceOf[recipient] += amount; + emit Transfer(sender, recipient, amount); + return true; + } + + function mint(uint amount) external { + balanceOf[msg.sender] += amount; + totalSupply += amount; + emit Transfer(address(0), msg.sender, amount); + } + + function burn(uint amount) external { + balanceOf[msg.sender] -= amount; + totalSupply -= amount; + emit Transfer(msg.sender, address(0), amount); + } +} diff --git a/crates/integration/contracts/flipper.sol b/crates/integration/contracts/flipper.sol new file mode 100644 index 0000000..b4fc578 --- /dev/null +++ b/crates/integration/contracts/flipper.sol @@ -0,0 +1,7 @@ +contract Flipper { + bool coin; + + function flip() public payable { + coin = !coin; + } +} diff --git a/crates/integration/src/lib.rs b/crates/integration/src/lib.rs new file mode 100644 index 0000000..d7eac82 --- /dev/null +++ b/crates/integration/src/lib.rs @@ -0,0 +1,95 @@ +pub mod mock_runtime; + +pub fn compile_blob(contract_name: &str, source_code: &str) -> Vec { + let file_name = "contract.sol"; + + let contracts = revive_solidity::test_utils::build_solidity( + [(file_name.into(), source_code.into())].into(), + Default::default(), + None, + revive_solidity::SolcPipeline::Yul, + era_compiler_llvm_context::OptimizerSettings::cycles(), + ) + .expect("source should compile") + .contracts + .expect("source should contain at least one contract"); + + let bytecode = contracts[file_name][contract_name] + .evm + .as_ref() + .expect("source should produce EVM output") + .assembly_text + .as_ref() + .expect("source should produce assembly text"); + + hex::decode(bytecode).expect("hex encoding should always be valid") +} + +#[cfg(test)] +mod tests { + use alloy_primitives::U256; + + use crate::mock_runtime::{self, State}; + + #[test] + fn flipper() { + let code = crate::compile_blob("Flipper", include_str!("../contracts/flipper.sol")); + let state = State::new(0xcde4efa9u32.to_be_bytes().to_vec()); + let (instance, export) = mock_runtime::prepare(&code); + + let state = crate::mock_runtime::call(state, &instance, export); + assert_eq!(state.output.flags, 0); + assert_eq!(state.storage[&U256::ZERO], U256::try_from(1).unwrap()); + + let state = crate::mock_runtime::call(state, &instance, export); + assert_eq!(state.output.flags, 0); + assert_eq!(state.storage[&U256::ZERO], U256::ZERO); + } + + #[test] + fn erc20() { + let _ = crate::compile_blob("ERC20", include_str!("../contracts/ERC20.sol")); + } + + #[test] + fn triangle_number() { + let code = crate::compile_blob("Computation", include_str!("../contracts/Computation.sol")); + let param = alloy_primitives::U256::try_from(13).unwrap(); + let expected = alloy_primitives::U256::try_from(91).unwrap(); + + // function triangle_number(int64) + let mut input = 0x0f760610u32.to_be_bytes().to_vec(); + input.extend_from_slice(¶m.to_be_bytes::<32>()); + + let state = State::new(input); + let (instance, export) = mock_runtime::prepare(&code); + let state = crate::mock_runtime::call(state, &instance, export); + + assert_eq!(state.output.flags, 0); + + let received = + alloy_primitives::U256::from_be_bytes::<32>(state.output.data.try_into().unwrap()); + assert_eq!(received, expected); + } + + #[test] + fn odd_product() { + let code = crate::compile_blob("Computation", include_str!("../contracts/Computation.sol")); + let param = alloy_primitives::I256::try_from(5i32).unwrap(); + let expected = alloy_primitives::I256::try_from(945i64).unwrap(); + + // function odd_product(int32) + let mut input = 0x00261b66u32.to_be_bytes().to_vec(); + input.extend_from_slice(¶m.to_be_bytes::<32>()); + + let state = State::new(input); + let (instance, export) = mock_runtime::prepare(&code); + let state = crate::mock_runtime::call(state, &instance, export); + + assert_eq!(state.output.flags, 0); + + let received = + alloy_primitives::I256::from_be_bytes::<32>(state.output.data.try_into().unwrap()); + assert_eq!(received, expected); + } +} diff --git a/crates/integration/src/mock_runtime.rs b/crates/integration/src/mock_runtime.rs new file mode 100644 index 0000000..4d7d716 --- /dev/null +++ b/crates/integration/src/mock_runtime.rs @@ -0,0 +1,204 @@ +//! Mock environment used for integration tests. +//! TODO: Switch to drink! once RISCV is ready in polkadot-sdk +use std::collections::HashMap; + +use alloy_primitives::U256; +use parity_scale_codec::Encode; +use polkavm::{ + Caller, Config, Engine, ExportIndex, GasMeteringKind, InstancePre, Linker, Module, + ModuleConfig, ProgramBlob, Trap, +}; + +#[derive(Default, Clone, Debug)] +pub struct State { + pub input: Vec, + pub output: CallOutput, + pub value: u128, + pub storage: HashMap, +} + +#[derive(Clone, Debug)] +pub struct CallOutput { + pub flags: u32, + pub data: Vec, +} + +impl Default for CallOutput { + fn default() -> Self { + Self { + flags: u32::MAX, + data: Vec::new(), + } + } +} + +impl State { + pub fn new(input: Vec) -> Self { + Self { + input, + ..Default::default() + } + } + + pub fn reset_output(&mut self) { + self.output = Default::default(); + } + + pub fn assert_storage_key(&self, at: U256, expect: U256) { + assert_eq!(self.storage[&at], expect); + } +} + +fn link_host_functions(engine: &Engine) -> Linker { + let mut linker = Linker::new(engine); + + linker + .func_wrap( + "input", + |caller: Caller, out_ptr: u32, out_len_ptr: u32| -> Result<(), Trap> { + let (mut caller, state) = caller.split(); + + assert_ne!(0, caller.read_u32(out_len_ptr)?); + + caller.write_memory(out_ptr, &state.input)?; + caller.write_memory(out_len_ptr, &(state.input.len() as u32).encode())?; + + Ok(()) + }, + ) + .unwrap(); + + linker + .func_wrap( + "seal_return", + |caller: Caller, flags: u32, data_ptr: u32, data_len: u32| -> Result<(), Trap> { + let (caller, state) = caller.split(); + + state.output.flags = flags; + state.output.data = caller.read_memory_into_vec(data_ptr, data_len)?; + + Err(Default::default()) + }, + ) + .unwrap(); + + linker + .func_wrap( + "value_transferred", + |caller: Caller, out_ptr: u32, out_len_ptr: u32| -> Result<(), Trap> { + let (mut caller, state) = caller.split(); + + let value = state.value.encode(); + + caller.write_memory(out_ptr, &value)?; + caller.write_memory(out_len_ptr, &(value.len() as u32).encode())?; + + Ok(()) + }, + ) + .unwrap(); + + linker + .func_wrap( + "debug_message", + |caller: Caller, str_ptr: u32, str_len: u32| -> Result { + let (caller, _) = caller.split(); + + let data = caller.read_memory_into_vec(str_ptr, str_len)?; + print!("debug_message: {}", String::from_utf8(data).unwrap()); + + Ok(0) + }, + ) + .unwrap(); + + linker + .func_wrap( + "set_storage", + |caller: Caller, + key_ptr: u32, + key_len: u32, + value_ptr: u32, + value_len: u32| + -> Result { + let (caller, state) = caller.split(); + + assert_eq!(key_len, 32, "storage key must be 32 bytes"); + assert_eq!(value_len, 32, "storage value must be 32 bytes"); + + let key = caller.read_memory_into_vec(key_ptr, key_len)?; + let value = caller.read_memory_into_vec(value_ptr, value_len)?; + + state.storage.insert( + U256::from_be_bytes::<32>(key.try_into().unwrap()), + U256::from_be_bytes::<32>(value.try_into().unwrap()), + ); + + Ok(0) + }, + ) + .unwrap(); + + linker + .func_wrap( + "get_storage", + |caller: Caller, + key_ptr: u32, + key_len: u32, + out_ptr: u32, + out_len_ptr: u32| + -> Result { + let (mut caller, state) = caller.split(); + + let key = caller.read_memory_into_vec(key_ptr, key_len)?; + let out_len = caller.read_u32(out_len_ptr)?; + assert!(out_len >= 32); + + let value = state + .storage + .get(&U256::from_be_bytes::<32>(key.try_into().unwrap())) + .map(U256::to_be_bytes::<32>) + .unwrap_or_default(); + + caller.write_memory(out_ptr, &value[..])?; + caller.write_memory(out_len_ptr, &32u32.to_le_bytes())?; + + Ok(0) + }, + ) + .unwrap(); + + linker +} + +pub fn prepare(code: &[u8]) -> (InstancePre, ExportIndex) { + let blob = ProgramBlob::parse(code).unwrap(); + + let engine = Engine::new(&Config::new()).unwrap(); + + let mut module_config = ModuleConfig::new(); + module_config.set_gas_metering(Some(GasMeteringKind::Sync)); + + let module = Module::from_blob(&engine, &module_config, &blob).unwrap(); + let export = module.lookup_export("call").unwrap(); + let func = link_host_functions(&engine) + .instantiate_pre(&module) + .unwrap(); + + (func, export) +} + +pub fn call(mut state: State, on: &InstancePre, export: ExportIndex) -> State { + state.reset_output(); + + let mut state_args = polkavm::StateArgs::default(); + state_args.set_gas(polkavm::Gas::MAX); + + let call_args = polkavm::CallArgs::new(&mut state, export); + + match on.instantiate().unwrap().call(state_args, call_args) { + Err(polkavm::ExecutionError::Trap(_)) => state, + Err(other) => panic!("unexpected error: {other}"), + Ok(_) => panic!("unexpected return"), + } +} diff --git a/crates/ir/Cargo.toml b/crates/ir/Cargo.toml deleted file mode 100644 index 6179feb..0000000 --- a/crates/ir/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -name = "revive-ir" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -evmil = { workspace = true } -petgraph = { workspace = true } -primitive-types = { workspace = true } -indexmap = { workspace = true } - -revive-compilation-target = { path = "../compilation-target" } diff --git a/crates/ir/src/address.rs b/crates/ir/src/address.rs deleted file mode 100644 index 54dc223..0000000 --- a/crates/ir/src/address.rs +++ /dev/null @@ -1,58 +0,0 @@ -use primitive_types::U256; - -#[derive(Clone, Copy)] -pub enum Kind { - Constant(U256), - Temporary(usize), - Stack, -} - -#[derive(Clone, Copy)] -pub struct Address { - pub kind: Kind, - pub type_hint: Option, -} - -impl From<(Kind, Option)> for Address { - fn from(value: (Kind, Option)) -> Self { - Self { - kind: value.0, - type_hint: value.1, - } - } -} - -impl Address { - pub fn new(kind: Kind, type_hint: Option) -> Self { - Self { kind, type_hint } - } -} - -#[derive(Clone, Copy)] -pub enum Type { - Int { size: u16 }, - Bytes { size: u8 }, - Bool, -} - -impl Type { - pub fn int(size: u16) -> Self { - Self::Int { size } - } - - fn bytes(size: u8) -> Self { - Self::Bytes { size } - } -} - -impl Default for Type { - fn default() -> Self { - Type::Bytes { size: 32 } - } -} - -pub enum LinearMemory { - CallData, - Memory, - ReturnData, -} diff --git a/crates/ir/src/analysis/control_flow.rs b/crates/ir/src/analysis/control_flow.rs deleted file mode 100644 index d3a463d..0000000 --- a/crates/ir/src/analysis/control_flow.rs +++ /dev/null @@ -1,60 +0,0 @@ -use indexmap::{IndexMap, IndexSet}; -use petgraph::prelude::*; - -use crate::{ - analysis::BlockAnalysis, - cfg::{Branch, Program}, - instruction::Instruction, - symbol::Kind, -}; - -/// Remove basic blocks not reachable from the start node. -#[derive(Default)] -pub struct ReachableCode(pub IndexSet); - -impl BlockAnalysis for ReachableCode { - fn analyze_block(&mut self, node: NodeIndex, _program: &mut Program) { - self.0.insert(node); - } - - fn apply_results(&mut self, program: &mut Program) { - program.cfg.graph.retain_nodes(|_, i| self.0.contains(&i)); - } -} - -/// Remove edges to the jump table if the jump target is statically known. -#[derive(Default)] -pub struct StaticJumps(IndexMap); - -impl BlockAnalysis for StaticJumps { - fn analyze_block(&mut self, node: NodeIndex, program: &mut Program) { - for edge in program.cfg.graph.edges(node) { - if *edge.weight() == Branch::Static { - continue; - } - - if let Some(Instruction::ConditionalBranch { target, .. }) - | Some(Instruction::UncoditionalBranch { target }) = - program.cfg.graph[node].instructions.last() - { - let Kind::Constant(bytecode_offset) = target.symbol().kind else { - continue; - }; - - let destination = program - .jump_targets - .get(&bytecode_offset.as_usize()) - .unwrap_or(&program.cfg.invalid_jump); - - self.0.insert(edge.id(), (node, *destination)); - } - } - } - - fn apply_results(&mut self, program: &mut Program) { - for (edge, (a, b)) in &self.0 { - program.cfg.graph.remove_edge(*edge); - program.cfg.graph.add_edge(*a, *b, Branch::Static); - } - } -} diff --git a/crates/ir/src/analysis/dominance.rs b/crates/ir/src/analysis/dominance.rs deleted file mode 100644 index 023f39c..0000000 --- a/crates/ir/src/analysis/dominance.rs +++ /dev/null @@ -1,19 +0,0 @@ -use crate::{cfg::Program, instruction::Instruction, symbol::Type, POINTER_SIZE}; -use petgraph::prelude::*; - -use super::BlockAnalysis; - -#[derive(Default)] -pub struct Unstack; - -impl BlockAnalysis for Unstack { - fn analyze_block(&mut self, node: NodeIndex, program: &mut Program) { - for instruction in &program.cfg.graph[node].instructions { - match instruction { - _ => {} - } - } - } - - fn apply_results(&mut self, _program: &mut Program) {} -} diff --git a/crates/ir/src/analysis/evm_bytecode.rs b/crates/ir/src/analysis/evm_bytecode.rs deleted file mode 100644 index 2f53725..0000000 --- a/crates/ir/src/analysis/evm_bytecode.rs +++ /dev/null @@ -1,373 +0,0 @@ -use indexmap::{IndexMap, IndexSet}; -use petgraph::prelude::*; - -use crate::{ - analysis::BlockAnalysis, - cfg::{Program, StackInfo}, - instruction::{Instruction, Operator}, - symbol::{Global, Kind, Symbol, SymbolBuilder, SymbolRef, SymbolTable}, -}; - -#[derive(Default)] -pub struct IrBuilder; - -impl BlockAnalysis for IrBuilder { - fn analyze_block(&mut self, node: NodeIndex, program: &mut Program) { - let mut builder = BlockBuilder::new(node, &mut program.symbol_table); - - for opcode in &program.evm_instructions[program.cfg.graph[node].opcodes.to_owned()] { - builder.translate(&opcode.instruction); - } - - let (instructions, stack_info) = builder.done(); - - program.cfg.graph[node].instructions = instructions; - program.cfg.graph[node].stack_info = stack_info; - } - - fn apply_results(&mut self, _program: &mut Program) {} -} - -pub struct BlockBuilder<'tbl> { - state: State<'tbl>, - instructions: Vec, -} - -impl<'tbl> BlockBuilder<'tbl> { - fn new(node: NodeIndex, symbol_table: &'tbl mut SymbolTable) -> Self { - Self { - state: State::new(node, symbol_table), - instructions: Default::default(), - } - } - - fn done(self) -> (Vec, StackInfo) { - let stack_info = StackInfo { - arguments: self.state.borrows, - generates: self.state.stack, - height: self.state.height, - }; - - assert_eq!( - stack_info.arguments as i32 + stack_info.height, - stack_info.generates.len() as i32, - "local stack elements must equal stack arguments taken + local height" - ); - - (self.instructions, stack_info) - } - - fn translate(&mut self, opcode: &evmil::bytecode::Instruction) { - use evmil::bytecode::Instruction::*; - self.instructions.extend(match opcode { - JUMPDEST => Vec::new(), - - PUSH(bytes) => { - self.state.push(Symbol::builder().constant(bytes)); - - Vec::new() - } - - POP => { - self.state.pop(); - - Vec::new() - } - - SWAP(n) => self.state.swap(*n as usize), - - DUP(n) => vec![Instruction::Copy { - y: self.state.nth(*n as usize), - x: self.state.push(Symbol::builder().variable()), - }], - - ADD => vec![Instruction::BinaryAssign { - y: self.state.pop(), - z: self.state.pop(), - x: self.state.push(Symbol::builder().variable()), - operator: Operator::Add, - }], - - SUB => vec![Instruction::BinaryAssign { - y: self.state.pop(), - z: self.state.pop(), - x: self.state.push(Symbol::builder().variable()), - operator: Operator::Sub, - }], - - MSTORE => vec![Instruction::IndexedAssign { - x: self.state.symbol_table.global(Global::Memory), - index: self.state.pop(), - y: self.state.pop(), - }], - - MLOAD => vec![Instruction::IndexedCopy { - index: self.state.pop(), - x: self.state.push(Symbol::builder().variable()), - y: self.state.symbol_table.global(Global::Memory), - }], - - JUMP => vec![Instruction::UncoditionalBranch { - target: self.state.pop(), - }], - - JUMPI => vec![Instruction::ConditionalBranch { - target: self.state.pop(), - condition: self.state.pop(), - }], - - CALLDATACOPY => vec![Instruction::Procedure { - symbol: Global::CallDataCopy, - parameters: vec![self.state.pop(), self.state.pop(), self.state.pop()], - }], - - CALLDATALOAD => vec![Instruction::IndexedCopy { - index: self.state.pop(), - x: self.state.push(Symbol::builder().variable()), - y: self.state.symbol_table.global(Global::CallData), - }], - - RETURN => vec![Instruction::Procedure { - symbol: Global::Return, - parameters: vec![self.state.pop(), self.state.pop()], - }], - - GT => vec![Instruction::BinaryAssign { - y: self.state.pop(), - z: self.state.pop(), - x: self.state.push(Symbol::builder().variable()), - operator: Operator::GreaterThan, - }], - - LT => vec![Instruction::BinaryAssign { - y: self.state.pop(), - z: self.state.pop(), - x: self.state.push(Symbol::builder().variable()), - operator: Operator::LessThan, - }], - - EQ => vec![Instruction::BinaryAssign { - y: self.state.pop(), - z: self.state.pop(), - x: self.state.push(Symbol::builder().variable()), - operator: Operator::Equal, - }], - - ISZERO => vec![Instruction::UnaryAssign { - y: self.state.pop(), - x: self.state.push(Symbol::builder().variable()), - operator: Operator::IsZero, - }], - - _ => { - eprintln!("unimplement instruction: {opcode}"); - Vec::new() - } - }) - } -} - -struct State<'tbl> { - node: NodeIndex, - symbol_table: &'tbl mut SymbolTable, - stack: Vec, - /// Every pop on an empty stack was counts as an additional argument. - borrows: usize, - /// Caches the arguments the block borrows from the stack. - arguments: IndexMap, - /// Tracks the relative stack height: - /// - Pushes increase the height by one - /// - Pops decrease the height by one - height: i32, -} - -impl<'tbl> State<'tbl> { - fn new(node: NodeIndex, symbol_table: &'tbl mut SymbolTable) -> Self { - Self { - node, - symbol_table, - stack: Default::default(), - borrows: Default::default(), - arguments: Default::default(), - height: Default::default(), - } - } - - fn pop(&mut self) -> SymbolRef { - self.height -= 1; - self.stack.pop().unwrap_or_else(|| { - self.borrows += 1; - self.nth(0) - }) - } - - fn push(&mut self, builder: SymbolBuilder<(), Kind>) -> SymbolRef { - let symbol = builder.temporary().done(); - let symbol = self.symbol_table.insert(self.node, symbol); - self.stack.push(symbol.clone()); - self.height += 1; - - symbol - } - - fn swap(&mut self, n: usize) -> Vec { - // For free if both elements are local to the basic block - let top = self.stack.len().saturating_sub(1); - if n <= top { - self.stack.swap(top - n, top); - return Vec::new(); - } - - let tmp = self.symbol_table.temporary(self.node); - let a = self.nth(0); - let b = self.nth(n); - - vec![ - Instruction::Copy { - x: tmp.clone(), - y: a.clone(), - }, - Instruction::Copy { x: a, y: b.clone() }, - Instruction::Copy { x: b, y: tmp }, - ] - } - - fn nth(&mut self, n: usize) -> SymbolRef { - self.stack - .iter() - .rev() - .nth(n) - .or_else(|| self.arguments.get(&(self.slot(n) as usize))) - .cloned() - .unwrap_or_else(|| { - let builder = Symbol::builder().stack(self.slot(n)).variable(); - let symbol = self.symbol_table.insert(self.node, builder.done()); - self.arguments.insert(self.slot(n) as usize, symbol.clone()); - symbol - }) - } - - fn slot(&self, n: usize) -> i32 { - n as i32 - (self.stack.len() as i32 - self.borrows as i32) - } -} - -#[cfg(test)] -mod tests { - use super::{BlockBuilder, State}; - use crate::{ - cfg::StackInfo, - instruction::Instruction, - symbol::{Symbol, SymbolTable}, - }; - use evmil::bytecode::Instruction::*; - - fn translate<'tbl>(code: &[evmil::bytecode::Instruction]) -> (Vec, StackInfo) { - code.iter() - .fold( - BlockBuilder::new(Default::default(), &mut SymbolTable::default()), - |mut builder, instruction| { - builder.translate(instruction); - builder - }, - ) - .done() - } - - #[test] - fn stack_slot_works() { - let mut symbol_table = SymbolTable::default(); - let mut state = State::new(Default::default(), &mut symbol_table); - - state.push(Symbol::builder().variable()); - assert_eq!(state.slot(0), -1); - assert_eq!(state.slot(1), 0); - assert_eq!(state.slot(2), 1); - - state.pop(); - state.pop(); - assert_eq!(state.slot(0), 1); - assert_eq!(state.slot(1), 2); - assert_eq!(state.slot(2), 3); - - state.push(Symbol::builder().variable()); - state.push(Symbol::builder().variable()); - assert_eq!(state.slot(0), -1); - assert_eq!(state.slot(1), 0); - assert_eq!(state.slot(2), 1); - } - - #[test] - fn push_works() { - let state = translate(&[PUSH(vec![1])]).1; - - assert_eq!(state.height, 1); - assert_eq!(state.arguments, 0); - assert_eq!(state.generates.len(), 1); - } - - #[test] - fn add_works() { - let state = translate(&[ADD]).1; - - assert_eq!(state.height, -1); - assert_eq!(state.arguments, 2); - assert_eq!(state.generates.len(), 1); - } - - #[test] - fn dup_works() { - let state = translate(&[DUP(4)]).1; - - assert_eq!(state.height, 1); - assert_eq!(state.arguments, 0); - assert_eq!(state.generates.len(), 1); - } - - #[test] - fn swap_works() { - let state = translate(&[SWAP(4)]).1; - - assert_eq!(state.height, 0); - assert_eq!(state.arguments, 0); - assert_eq!(state.generates.len(), 0); - } - - #[test] - fn jump() { - let state = translate(&[JUMP]).1; - - assert_eq!(state.height, -1); - assert_eq!(state.arguments, 1); - assert_eq!(state.generates.len(), 0); - } - - #[test] - fn pop5_push2() { - let state = translate(&[POP, POP, POP, POP, POP, PUSH(vec![1]), PUSH(vec![1])]).1; - - assert_eq!(state.height, -3); - assert_eq!(state.arguments, 5); - assert_eq!(state.generates.len(), 2); - } - - #[test] - fn fibonacci_loop_body() { - let state = translate(&[ - PUSH(vec![1]), - ADD, - SWAP(2), - DUP(1), - SWAP(4), - ADD, - SWAP(2), - PUSH(vec![10]), - JUMP, - ]) - .1; - - assert_eq!(state.height, 0); - assert_eq!(state.arguments, 1); - assert_eq!(state.generates.len(), 1); - } -} diff --git a/crates/ir/src/analysis/mod.rs b/crates/ir/src/analysis/mod.rs deleted file mode 100644 index 2a56c32..0000000 --- a/crates/ir/src/analysis/mod.rs +++ /dev/null @@ -1,31 +0,0 @@ -use petgraph::prelude::*; - -use crate::cfg::Program; - -pub mod control_flow; -pub mod dominance; -pub mod evm_bytecode; -pub mod types; - -/// The analyzer visits each basic block using DFS. -pub trait BlockAnalysis: Default { - fn analyze_block(&mut self, node: NodeIndex, program: &mut Program); - - fn apply_results(&mut self, program: &mut Program); -} - -pub fn analyze(program: &mut Program) -> Pass -where - Pass: BlockAnalysis, -{ - let mut dfs = Dfs::new(&program.cfg.graph, program.cfg.start); - let mut pass = Pass::default(); - - while let Some(node) = dfs.next(&program.cfg.graph) { - pass.analyze_block(node, program); - } - - pass.apply_results(program); - - pass -} diff --git a/crates/ir/src/analysis/tail_edges.rs b/crates/ir/src/analysis/tail_edges.rs deleted file mode 100644 index 55170ac..0000000 --- a/crates/ir/src/analysis/tail_edges.rs +++ /dev/null @@ -1,10 +0,0 @@ -use indexmap::IndexMap; -use petgraph::prelude::*; - -use crate::{ - cfg::{Branch, Program}, - instruction::Instruction, - symbol::Kind, -}; - -use super::BlockAnalysis; diff --git a/crates/ir/src/analysis/types.rs b/crates/ir/src/analysis/types.rs deleted file mode 100644 index af00cd1..0000000 --- a/crates/ir/src/analysis/types.rs +++ /dev/null @@ -1,42 +0,0 @@ -use crate::{cfg::Program, instruction::Instruction, symbol::Type, POINTER_SIZE}; -use petgraph::prelude::*; - -use super::BlockAnalysis; - -#[derive(Default)] -pub struct TypePropagation; - -impl BlockAnalysis for TypePropagation { - fn analyze_block(&mut self, node: NodeIndex, program: &mut Program) { - for instruction in &program.cfg.graph[node].instructions { - match instruction { - Instruction::ConditionalBranch { condition, target } => { - condition.replace_type(Type::Bool); - target.replace_type(Type::Int(POINTER_SIZE)); - } - - Instruction::UncoditionalBranch { target } => { - target.replace_type(Type::Int(POINTER_SIZE)); - } - - Instruction::BinaryAssign { x, y, z, .. } => { - y.replace_type(x.symbol().type_hint); - z.replace_type(x.symbol().type_hint); - } - - Instruction::Copy { x, y } | Instruction::UnaryAssign { x, y, .. } => { - x.replace_type(y.symbol().type_hint); - } - - Instruction::IndexedCopy { index, .. } - | Instruction::IndexedAssign { index, .. } => { - index.replace_type(Type::Int(POINTER_SIZE)) - } - - _ => {} - } - } - } - - fn apply_results(&mut self, _program: &mut Program) {} -} diff --git a/crates/ir/src/cfg.rs b/crates/ir/src/cfg.rs deleted file mode 100644 index 314106d..0000000 --- a/crates/ir/src/cfg.rs +++ /dev/null @@ -1,241 +0,0 @@ -use std::fmt::Write; -use std::ops::Range; - -use indexmap::IndexMap; -use petgraph::dot::{Config, Dot}; -use petgraph::prelude::*; - -use crate::pass::dead_code::DeadCodeElimination; -use crate::pass::lift::BytecodeLifter; -use crate::pass::Pass; -use crate::symbol::SymbolRef; -use crate::{instruction::Instruction, symbol::SymbolTable}; - -pub struct Cfg { - pub graph: StableDiGraph, - pub start: NodeIndex, - pub jump_table: NodeIndex, - pub terminator: NodeIndex, - pub invalid_jump: NodeIndex, -} - -#[derive(Debug, PartialEq)] -pub enum Branch { - Static, - Dynamic, -} - -impl Default for Cfg { - fn default() -> Self { - let mut graph = StableDiGraph::new(); - - Self { - start: graph.add_node(Default::default()), - jump_table: graph.add_node(Default::default()), - terminator: graph.add_node(Default::default()), - invalid_jump: graph.add_node(Default::default()), - graph, - } - } -} - -#[derive(Clone, Debug)] -pub struct EvmInstruction { - pub bytecode_offset: usize, - pub instruction: evmil::bytecode::Instruction, -} - -#[derive(Debug, Default)] -pub struct BasicBlock { - pub opcodes: Range, - pub instructions: Vec, - pub stack_info: StackInfo, -} - -#[derive(Debug, Default)] -pub struct StackInfo { - pub arguments: usize, - pub generates: Vec, - pub height: i32, -} - -impl BasicBlock { - fn linear_at(start: usize) -> Self { - Self { - opcodes: start..start + 1, - ..Default::default() - } - } - - fn format(&self, evm_bytecode: &[EvmInstruction], options: BasicBlockFormatOption) -> String { - match options { - BasicBlockFormatOption::ByteCode => evm_bytecode[self.opcodes.start..self.opcodes.end] - .iter() - .fold(String::new(), |mut acc, opcode| { - writeln!(&mut acc, "{:?}", opcode.instruction).unwrap(); - acc - }), - BasicBlockFormatOption::Ir => { - self.instructions - .iter() - .fold(String::new(), |mut acc, instruction| { - writeln!(&mut acc, "{instruction}").unwrap(); - acc - }) - } - _ => String::new(), - } - } -} - -#[derive(Clone, Copy, Default)] -pub enum BasicBlockFormatOption { - ByteCode, - Ir, - #[default] - None, -} - -pub struct Program { - pub evm_instructions: Vec, - pub cfg: Cfg, - pub symbol_table: SymbolTable, - pub jump_targets: IndexMap, -} - -impl Program { - /// Create a new [Program] from EVM bytecode. - /// - /// - Dynamic jumps reach the dynamic jump table - /// - `JUMPDEST` and `JUMPI` split up the node - /// - Instructions not returning reach the terminator node - pub fn new(bytecode: &[evmil::bytecode::Instruction]) -> Self { - let mut evm_instructions = Vec::with_capacity(bytecode.len()); - let mut cfg = Cfg::default(); - let mut jump_targets = IndexMap::default(); - let mut bytecode_offset = 0; - let mut node = cfg.graph.add_node(Default::default()); - cfg.graph.add_edge(cfg.start, node, Branch::Static); - cfg.graph - .add_edge(cfg.invalid_jump, cfg.terminator, Branch::Static); - cfg.graph - .add_edge(cfg.jump_table, cfg.invalid_jump, Branch::Dynamic); - - for (index, opcode) in bytecode.iter().enumerate() { - evm_instructions.push(EvmInstruction { - bytecode_offset, - instruction: opcode.clone(), - }); - cfg.graph[node].opcodes.end = index + 1; - - use evmil::bytecode::Instruction::*; - match opcode { - // The preceding instruction did already split up control flow - JUMPDEST - if matches!( - evm_instructions[index.saturating_sub(1)].instruction, - JUMP | JUMPI | RETURN | REVERT | INVALID | STOP | SELFDESTRUCT - ) => - { - cfg.graph.add_edge(cfg.jump_table, node, Branch::Dynamic); - - jump_targets.insert(bytecode_offset, node); - } - - JUMPDEST => { - cfg.graph[node].opcodes.end = index; - let previous_node = node; - node = cfg.graph.add_node(BasicBlock::linear_at(index)); - - cfg.graph.add_edge(cfg.jump_table, node, Branch::Dynamic); - cfg.graph.add_edge(previous_node, node, Branch::Static); - - jump_targets.insert(bytecode_offset, node); - } - - JUMP => { - cfg.graph.add_edge(node, cfg.jump_table, Branch::Dynamic); - - node = cfg.graph.add_node(BasicBlock::linear_at(index + 1)); - } - - JUMPI => { - cfg.graph.add_edge(node, cfg.jump_table, Branch::Dynamic); - - let previous_node = node; - node = cfg.graph.add_node(BasicBlock::linear_at(index + 1)); - cfg.graph.add_edge(previous_node, node, Branch::Static); - } - - STOP | RETURN | REVERT | INVALID | SELFDESTRUCT => { - cfg.graph.add_edge(node, cfg.terminator, Branch::Static); - - node = cfg.graph.add_node(BasicBlock::linear_at(index + 1)); - } - - _ => {} - } - - bytecode_offset += opcode.length(); - } - - Self { - evm_instructions, - cfg, - symbol_table: Default::default(), - jump_targets, - } - } - - pub fn optimize(&mut self) { - DeadCodeElimination::run(&mut Default::default(), self); - BytecodeLifter::run(&mut Default::default(), self); - DeadCodeElimination::run(&mut Default::default(), self) - } - - pub fn dot(&self, format_options: BasicBlockFormatOption) { - let get_node_attrs = move |_, (index, node): (_, &BasicBlock)| { - let (color, shape, label) = if index == self.cfg.terminator { - ("red", "oval", "Terminator".to_string()) - } else if index == self.cfg.start { - ("red", "oval", "Start".to_string()) - } else if index == self.cfg.invalid_jump { - ("blue", "hexagon", "Invalid jump target".to_string()) - } else if index == self.cfg.jump_table { - ("blue", "diamond", "Dynamic jump table".to_string()) - } else { - let instructions = node.format(&self.evm_instructions, format_options); - let start = &self.evm_instructions[node.opcodes.start].bytecode_offset; - let end = &self - .evm_instructions - .get(node.opcodes.end) - .unwrap_or_else(|| &self.evm_instructions[node.opcodes.end - 1]) - .bytecode_offset; - ( - "black", - "rectangle", - format!("Bytecode (0x{start:02x}, 0x{end:02x}]\n---\n{instructions}",), - ) - }; - - format!("color={color} shape={shape} label=\"{label}\"",) - }; - - let get_edge_attrs = |_, edge: petgraph::stable_graph::EdgeReference<'_, Branch>| { - let style = match edge.weight() { - Branch::Static => "solid", - Branch::Dynamic => "dashed", - }; - format!("style={style}") - }; - - let dot = Dot::with_attr_getters( - &self.cfg.graph, - &[Config::EdgeNoLabel, Config::NodeNoLabel], - &get_edge_attrs, - &get_node_attrs, - ); - - println!("{dot:?}"); - } -} diff --git a/crates/ir/src/instruction.rs b/crates/ir/src/instruction.rs deleted file mode 100644 index d6383f0..0000000 --- a/crates/ir/src/instruction.rs +++ /dev/null @@ -1,143 +0,0 @@ -use crate::symbol::{Global, SymbolRef}; -use std::fmt::Write; - -#[derive(PartialEq, Debug)] -pub enum Instruction { - Nop, - - /// `x = y op z` - BinaryAssign { - x: SymbolRef, - y: SymbolRef, - operator: Operator, - z: SymbolRef, - }, - - /// `x = op y` - UnaryAssign { - x: SymbolRef, - operator: Operator, - y: SymbolRef, - }, - - /// `branch target` - UncoditionalBranch { - target: SymbolRef, - }, - - /// `branch target if condition` - ConditionalBranch { - condition: SymbolRef, - target: SymbolRef, - }, - - /// `call(label, n)` - Procedure { - symbol: Global, - parameters: Vec, - }, - - /// `x = call(label, n)` - Function { - symbol: Global, - x: SymbolRef, - parameters: Vec, - }, - - /// `x = y` - Copy { - x: SymbolRef, - y: SymbolRef, - }, - - /// `x[index] = y` - IndexedAssign { - x: SymbolRef, - index: SymbolRef, - y: SymbolRef, - }, - - /// `x = y[index]` - IndexedCopy { - x: SymbolRef, - y: SymbolRef, - index: SymbolRef, - }, -} - -impl std::fmt::Display for Instruction { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::BinaryAssign { x, y, operator, z } => write!(f, "{x} = {y} {operator:?} {z}"), - - Self::UnaryAssign { x, operator, y } => write!(f, "{x} = {operator:?} {y} "), - - Self::UncoditionalBranch { target } => write!(f, "branch {target}"), - - Self::ConditionalBranch { condition, target } => { - write!(f, "if {condition} branch {target}") - } - - Self::Procedure { symbol, parameters } => write!( - f, - "{symbol:?}({})", - parameters.iter().fold(String::new(), |mut acc, p| { - write!(&mut acc, "{p}, ").unwrap(); - acc - }) - ), - - Self::Function { - symbol, - x, - parameters: args, - } => write!( - f, - "{x} = {symbol:?}({})", - args.iter().fold(String::new(), |mut acc, p| { - write!(&mut acc, "{p}, ").unwrap(); - acc - }) - ), - - Self::Copy { x, y } => write!(f, "{x} = {y}"), - - Self::IndexedAssign { x, index, y } => write!(f, "{x}[{index}] = {y}"), - - Self::IndexedCopy { x, y, index } => write!(f, "{x} = {y}[{index}]"), - - Self::Nop => write!(f, "no-op"), - } - } -} - -#[derive(PartialEq, Debug)] -pub enum Operator { - Add, - Mul, - Sub, - Div, - SDiv, - Mod, - SMod, - AddMod, - MulMod, - Exp, - SignExtend, - - LessThan, - GreaterThan, - SignedLessThan, - SignedGreaterThan, - Equal, - IsZero, - - And, - Or, - Xor, - Not, - Byte, - ShiftLeft, - ShiftRight, - ShiftArithmeticRight, -} diff --git a/crates/ir/src/lib.rs b/crates/ir/src/lib.rs deleted file mode 100644 index 58c56c2..0000000 --- a/crates/ir/src/lib.rs +++ /dev/null @@ -1,14 +0,0 @@ -pub mod analysis; -pub mod cfg; -pub mod instruction; -pub mod pass; -pub mod symbol; - -pub static POINTER_SIZE: usize = 32; - -#[cfg(test)] -mod tests { - - #[test] - fn it_works() {} -} diff --git a/crates/ir/src/pass/dead_code.rs b/crates/ir/src/pass/dead_code.rs deleted file mode 100644 index 7533175..0000000 --- a/crates/ir/src/pass/dead_code.rs +++ /dev/null @@ -1,15 +0,0 @@ -use crate::{ - analysis::{analyze, control_flow::ReachableCode}, - cfg::Program, -}; - -use super::Pass; - -#[derive(Default)] -pub struct DeadCodeElimination; - -impl Pass for DeadCodeElimination { - fn run(&mut self, program: &mut Program) { - analyze::(program); - } -} diff --git a/crates/ir/src/pass/lift.rs b/crates/ir/src/pass/lift.rs deleted file mode 100644 index db54899..0000000 --- a/crates/ir/src/pass/lift.rs +++ /dev/null @@ -1,19 +0,0 @@ -use crate::{ - analysis::{ - analyze, control_flow::StaticJumps, evm_bytecode::IrBuilder, types::TypePropagation, - }, - cfg::Program, -}; - -use super::Pass; - -#[derive(Default)] -pub struct BytecodeLifter; - -impl Pass for BytecodeLifter { - fn run(&mut self, program: &mut Program) { - analyze::(program); - analyze::(program); - analyze::(program); - } -} diff --git a/crates/ir/src/pass/mod.rs b/crates/ir/src/pass/mod.rs deleted file mode 100644 index 3f136d9..0000000 --- a/crates/ir/src/pass/mod.rs +++ /dev/null @@ -1,8 +0,0 @@ -use crate::cfg::Program; - -pub mod dead_code; -pub mod lift; - -pub trait Pass: Default { - fn run(&mut self, program: &mut Program); -} diff --git a/crates/ir/src/symbol.rs b/crates/ir/src/symbol.rs deleted file mode 100644 index 72d665b..0000000 --- a/crates/ir/src/symbol.rs +++ /dev/null @@ -1,317 +0,0 @@ -use indexmap::IndexMap; -use petgraph::prelude::NodeIndex; -use primitive_types::U256; -use std::{cell::RefCell, rc::Rc}; - -use crate::POINTER_SIZE; - -#[derive(Debug, Default)] -pub struct SymbolTable { - table: IndexMap>>>, - symbols: IndexMap>>, - global_scope: NodeIndex, - id_nonce: usize, -} - -impl SymbolTable { - pub fn merge_scopes(&mut self, node: NodeIndex, target: NodeIndex) { - let sym = self.symbols.remove(&0).unwrap(); - let new = self - .table - .get(&NodeIndex::default()) - .unwrap() - .get(&0) - .unwrap(); - //RefCell::replace(&sym, Rc::clone(new)); - } - - pub fn get_symbol(&self, id: usize) -> SymbolRef { - SymbolRef { - inner: self.symbols.get(&id).unwrap().clone(), - id, - } - } - - pub fn insert(&mut self, scope: NodeIndex, symbol: Symbol) -> SymbolRef { - let id = self.next(); - let inner = Rc::new(RefCell::new(symbol)); - - self.table - .entry(scope) - .or_default() - .insert(id, Rc::clone(&inner)); - self.symbols.insert(id, inner.clone()); - - SymbolRef { inner, id } - } - - pub fn global(&mut self, label: Global) -> SymbolRef { - self.table - .entry(self.global_scope) - .or_default() - .iter() - .find(|(_, symbol)| symbol.borrow().address == Address::Label(label)) - .map(|(id, _)| *id) - .map(|id| self.get_symbol(id)) - .unwrap_or_else(|| self.insert(self.global_scope, Symbol::builder().global(label))) - } - - pub fn temporary(&mut self, node: NodeIndex) -> SymbolRef { - self.insert(node, Symbol::builder().temporary().variable().done()) - } - - fn next(&mut self) -> usize { - let current = self.id_nonce; - self.id_nonce += 1; - current - } -} - -#[derive(Default)] -pub struct SymbolBuilder { - address: A, - type_hint: Type, - kind: K, -} - -impl SymbolBuilder<(), K> { - pub fn temporary(self) -> SymbolBuilder { - SymbolBuilder { - address: Address::Temporary, - type_hint: self.type_hint, - kind: self.kind, - } - } - - pub fn stack(self, slot: i32) -> SymbolBuilder { - SymbolBuilder { - address: Address::Stack(slot), - type_hint: self.type_hint, - kind: self.kind, - } - } - - pub fn global(self, label: Global) -> Symbol { - Symbol { - address: Address::Label(label), - type_hint: label.typ(), - kind: label.kind(), - } - } -} - -impl SymbolBuilder { - pub fn constant(self, bytes: &[u8]) -> SymbolBuilder { - SymbolBuilder { - address: self.address, - type_hint: Type::Bytes(bytes.len()), - kind: Kind::Constant(U256::from_big_endian(bytes)), - } - } - - pub fn variable(self) -> SymbolBuilder { - SymbolBuilder { - address: self.address, - type_hint: self.type_hint, - kind: Kind::Variable, - } - } -} - -impl SymbolBuilder { - pub fn of(self, type_hint: Type) -> Self { - Self { type_hint, ..self } - } -} - -impl SymbolBuilder { - pub fn done(self) -> Symbol { - Symbol { - address: self.address, - type_hint: self.type_hint, - kind: self.kind, - } - } -} - -#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] -pub struct Symbol { - pub address: Address, - pub type_hint: Type, - pub kind: Kind, -} - -impl Symbol { - pub fn builder() -> SymbolBuilder { - Default::default() - } -} - -#[derive(Clone, Debug)] -pub struct SymbolRef { - inner: Rc>, - id: usize, -} - -impl std::fmt::Display for SymbolRef { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let symbol = self.symbol(); - - let address = format!("${}_{}", self.id, symbol.address); - - match symbol.kind { - Kind::Pointer => write!(f, "*{address}"), - Kind::Constant(value) => { - write!(f, "{} {address} := {value}", symbol.type_hint) - } - _ => write!(f, "{} {address} ", symbol.type_hint), - } - } -} - -impl SymbolRef { - pub fn replace_type(&self, type_hint: Type) { - self.inner.replace_with(|old| Symbol { - address: old.address, - kind: old.kind, - type_hint, - }); - } - - pub fn symbol(&self) -> Symbol { - *self.inner.borrow() - } - - pub fn id(&self) -> usize { - self.id - } -} - -impl PartialEq for SymbolRef { - fn eq(&self, other: &Self) -> bool { - self.id == other.id - } -} - -#[derive(Default, Debug, PartialEq, Eq, Hash, Clone, Copy)] -pub enum Address { - #[default] - Temporary, - Stack(i32), - Label(Global), -} - -impl std::fmt::Display for Address { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Stack(slot) => write!(f, "stack[{slot}]"), - Self::Temporary => write!(f, "tmp"), - Self::Label(label) => write!(f, "{label:?}"), - } - } -} - -#[derive(Debug, PartialEq, Eq, Hash, Default, Clone, Copy)] -pub enum Type { - #[default] - Word, - UInt(usize), - Int(usize), - Bytes(usize), - Bool, -} - -impl std::fmt::Display for Type { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Word => write!(f, "word"), - Self::UInt(size) => write!(f, "u{}", size), - Self::Int(size) => write!(f, "i{}", size), - Self::Bytes(size) => write!(f, "bytes{size}"), - Self::Bool => write!(f, "bool"), - } - } -} - -impl Type { - pub fn pointer() -> Self { - Self::UInt(POINTER_SIZE) - } -} - -#[derive(Default, Debug, PartialEq, Eq, Hash, Clone, Copy)] -pub enum Kind { - Pointer, - #[default] - Variable, - Constant(U256), - Function, -} - -#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] -pub enum Global { - Stack, - StackHeight, - - CallData, - Memory, - ReturnData, - - MemoryCopy, - - // EVM runtime environment - Sha3, - Address, - CallDataLoad, - CallDataSize, - CallDataCopy, - CodeSize, - CodeCopy, - GasPrice, - ExtCodeSize, - ExtCodeCopy, - ReturnDataSize, - ReturnDataCopy, - ExtCodeHash, - BlockHash, - Coinbase, - Timestamp, - BlockNumber, - PrevRanDao, - GasLimit, - ChainId, - SelfBalance, - BaseFee, - SLoad, - SStore, - Gas, - Create, - Create2, - Call, - StaticCall, - DelegateCall, - CallCode, - Return, - Stop, - Revert, - SelfDestruct, - Event, -} - -impl Global { - pub fn typ(&self) -> Type { - match self { - Self::Stack | Self::CallData | Self::Memory | Self::ReturnData => Type::pointer(), - Self::StackHeight => Type::UInt(POINTER_SIZE), - _ => Type::Word, - } - } - - pub fn kind(&self) -> Kind { - match self { - Self::Stack | Self::CallData | Self::Memory | Self::ReturnData => Kind::Pointer, - Self::StackHeight => Kind::Variable, - _ => Kind::Function, - } - } -} diff --git a/crates/target-polkavm/Cargo.toml b/crates/linker/Cargo.toml similarity index 74% rename from crates/target-polkavm/Cargo.toml rename to crates/linker/Cargo.toml index 03021e0..2574b2a 100644 --- a/crates/target-polkavm/Cargo.toml +++ b/crates/linker/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "revive-target-polkavm" +name = "revive-linker" version = "0.1.0" edition = "2021" @@ -11,8 +11,7 @@ tempfile = { workspace = true } polkavm-linker = { workspace = true } polkavm-common = { workspace = true } libc = { workspace = true } +anyhow = { workspace = true } -revive-codegen = { path = "../codegen" } -revive-compilation-target = { path = "../compilation-target" } revive-builtins = { path = "../builtins" } lld-sys = { path = "../lld-sys" } diff --git a/crates/target-polkavm/polkavm_guest.bc b/crates/linker/polkavm_guest.bc similarity index 100% rename from crates/target-polkavm/polkavm_guest.bc rename to crates/linker/polkavm_guest.bc diff --git a/crates/target-polkavm/src/linker.rs b/crates/linker/src/lib.rs similarity index 57% rename from crates/target-polkavm/src/linker.rs rename to crates/linker/src/lib.rs index 1961595..a09e80b 100644 --- a/crates/target-polkavm/src/linker.rs +++ b/crates/linker/src/lib.rs @@ -1,25 +1,11 @@ -use std::{ffi::CString, fs}; +use std::{env, ffi::CString, fs}; use lld_sys::LLDELFLink; use revive_builtins::COMPILER_RT; const LINKER_SCRIPT: &str = r#" SECTIONS { - . = 0x10000; - .rodata : { *(.rodata) *(.rodata.*) } - .data.rel.ro : { *(.data.rel.ro) *(.data.rel.ro.*) } - .got : { *(.got) *(.got.*) } - - . = ALIGN(0x4000); - .data : { *(.sdata) *(.data) } - .bss : { *(.sbss) *(.bss) *(.bss.*) } - - . = 0xf0000000; - .text : { KEEP(*(.text.polkavm_export)) *(.text .text.*) } - - /DISCARD/ : { *(.eh_frame) } - . = ALIGN(4); }"#; fn invoke_lld(cmd_args: &[&str]) -> bool { @@ -33,30 +19,33 @@ fn invoke_lld(cmd_args: &[&str]) -> bool { unsafe { LLDELFLink(args.as_ptr(), args.len()) == 0 } } -fn polkavm_linker(code: &[u8]) -> Vec { +fn polkavm_linker>(code: T) -> anyhow::Result> { let mut config = polkavm_linker::Config::default(); config.set_strip(true); - match polkavm_linker::program_from_elf(config, code) { - Ok(blob) => blob.as_bytes().to_vec(), - Err(reason) => panic!("polkavm linker failed: {}", reason), - } + polkavm_linker::program_from_elf(config, code.as_ref()) + .map(|blob| blob.as_bytes().to_vec()) + .map_err(|reason| anyhow::anyhow!("polkavm linker failed: {}", reason)) } -pub(crate) fn link(input: &[u8]) -> Vec { +pub fn link>(input: T) -> anyhow::Result> { let dir = tempfile::tempdir().expect("failed to create temp directory for linking"); let output_path = dir.path().join("out.so"); let object_path = dir.path().join("out.o"); let linker_script_path = dir.path().join("linker.ld"); let compiler_rt_path = dir.path().join("libclang_rt.builtins-riscv32.a"); - fs::write(&object_path, input).unwrap_or_else(|msg| panic!("{msg} {object_path:?}")); + fs::write(&object_path, input).map_err(|msg| anyhow::anyhow!("{msg} {object_path:?}"))?; + + if env::var("PVM_LINKER_DUMP_OBJ").is_ok() { + fs::copy(&object_path, "/tmp/out.o")?; + } fs::write(&linker_script_path, LINKER_SCRIPT) - .unwrap_or_else(|msg| panic!("{msg} {linker_script_path:?}")); + .map_err(|msg| anyhow::anyhow!("{msg} {linker_script_path:?}"))?; fs::write(&compiler_rt_path, COMPILER_RT) - .unwrap_or_else(|msg| panic!("{msg} {compiler_rt_path:?}")); + .map_err(|msg| anyhow::anyhow!("{msg} {compiler_rt_path:?}"))?; let ld_args = [ "ld.lld", @@ -75,12 +64,14 @@ pub(crate) fn link(input: &[u8]) -> Vec { output_path.to_str().expect("should be utf8"), ]; - assert!(!invoke_lld(&ld_args), "ld.lld failed"); + if invoke_lld(&ld_args) { + return Err(anyhow::anyhow!("ld.lld failed")); + } - fs::copy(&object_path, "/tmp/out.o").unwrap(); - fs::copy(&output_path, "/tmp/out.so").unwrap(); - fs::copy(&linker_script_path, "/tmp/linkder.ld").unwrap(); + if env::var("PVM_LINKER_DUMP_SO").is_ok() { + fs::copy(&output_path, "/tmp/out.so")?; + }; - let blob = fs::read(&output_path).expect("ld.lld should produce output"); - polkavm_linker(&blob) + let blob = fs::read(&output_path)?; + polkavm_linker(blob) } diff --git a/crates/llvm-context/Cargo.toml b/crates/llvm-context/Cargo.toml new file mode 100644 index 0000000..d10e08b --- /dev/null +++ b/crates/llvm-context/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "era-compiler-llvm-context" +version = "1.4.1" +authors = [ + "Oleksandr Zarudnyi ", +] +license = "MIT OR Apache-2.0" +edition = "2021" +description = "Shared front end code of the EraVM compilers" + +[lib] +doctest = false + +[dependencies] +anyhow = { workspace = true } +semver = { workspace = true } +itertools = { workspace = true } +serde = { workspace = true, features = ["derive"] } +regex = { workspace = true } +once_cell = { workspace = true } +num = { workspace = true } +hex = { workspace = true } +sha2 = { workspace = true } +sha3 = { workspace = true } +md5 = { workspace = true } +inkwell = { workspace = true } + +zkevm_opcode_defs = { git = "https://github.com/matter-labs/era-zkevm_opcode_defs", branch = "v1.4.1" } +era-compiler-common = { git = "https://github.com/matter-labs/era-compiler-common", branch = "main" } + +pallet-contracts-pvm-llapi = { path = "../pallet-contracts-pvm-llapi" } +revive-linker = { path = "../linker" } +revive-builtins = { path = "../builtins" } +revive-stdlib = { path = "../stdlib" } diff --git a/crates/llvm-context/src/debug_config/ir_type.rs b/crates/llvm-context/src/debug_config/ir_type.rs new file mode 100644 index 0000000..7b1fd6f --- /dev/null +++ b/crates/llvm-context/src/debug_config/ir_type.rs @@ -0,0 +1,40 @@ +//! +//! The debug IR type. +//! + +/// +/// The debug IR type. +/// +#[allow(non_camel_case_types)] +#[allow(clippy::upper_case_acronyms)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum IRType { + /// Whether to dump the Yul code. + Yul, + /// Whether to dump the EVM legacy assembly code. + EVMLA, + /// Whether to dump the Ethereal IR code. + EthIR, + /// Whether to dump the Vyper LLL IR code. + LLL, + /// Whether to dump the LLVM IR code. + LLVM, + /// Whether to dump the assembly code. + Assembly, +} + +impl IRType { + /// + /// Returns the file extension for the specified IR. + /// + pub fn file_extension(&self) -> &'static str { + match self { + Self::Yul => era_compiler_common::EXTENSION_YUL, + Self::EthIR => era_compiler_common::EXTENSION_ETHIR, + Self::EVMLA => era_compiler_common::EXTENSION_EVMLA, + Self::LLL => era_compiler_common::EXTENSION_LLL, + Self::LLVM => era_compiler_common::EXTENSION_LLVM_SOURCE, + Self::Assembly => era_compiler_common::EXTENSION_ERAVM_ASSEMBLY, + } + } +} diff --git a/crates/llvm-context/src/debug_config/mod.rs b/crates/llvm-context/src/debug_config/mod.rs new file mode 100644 index 0000000..5956fae --- /dev/null +++ b/crates/llvm-context/src/debug_config/mod.rs @@ -0,0 +1,140 @@ +//! +//! The debug configuration. +//! + +pub mod ir_type; + +use std::path::PathBuf; + +use serde::Deserialize; +use serde::Serialize; + +use self::ir_type::IRType; + +/// +/// The debug configuration. +/// +#[derive(Debug, Default, Serialize, Deserialize, Clone)] +pub struct DebugConfig { + /// The directory to dump the IRs to. + pub output_directory: PathBuf, +} + +impl DebugConfig { + /// + /// A shortcut constructor. + /// + pub fn new(output_directory: PathBuf) -> Self { + Self { output_directory } + } + + /// + /// Dumps the Yul IR. + /// + pub fn dump_yul(&self, contract_path: &str, code: &str) -> anyhow::Result<()> { + let mut file_path = self.output_directory.to_owned(); + let full_file_name = Self::full_file_name(contract_path, None, IRType::Yul); + file_path.push(full_file_name); + std::fs::write(file_path, code)?; + + Ok(()) + } + + /// + /// Dumps the EVM legacy assembly IR. + /// + pub fn dump_evmla(&self, contract_path: &str, code: &str) -> anyhow::Result<()> { + let mut file_path = self.output_directory.to_owned(); + let full_file_name = Self::full_file_name(contract_path, None, IRType::EVMLA); + file_path.push(full_file_name); + std::fs::write(file_path, code)?; + + Ok(()) + } + + /// + /// Dumps the Ethereal IR. + /// + pub fn dump_ethir(&self, contract_path: &str, code: &str) -> anyhow::Result<()> { + let mut file_path = self.output_directory.to_owned(); + let full_file_name = Self::full_file_name(contract_path, None, IRType::EthIR); + file_path.push(full_file_name); + std::fs::write(file_path, code)?; + + Ok(()) + } + + /// + /// Dumps the LLL IR. + /// + pub fn dump_lll(&self, contract_path: &str, code: &str) -> anyhow::Result<()> { + let mut file_path = self.output_directory.to_owned(); + let full_file_name = Self::full_file_name(contract_path, None, IRType::LLL); + file_path.push(full_file_name); + std::fs::write(file_path, code)?; + + Ok(()) + } + + /// + /// Dumps the unoptimized LLVM IR. + /// + pub fn dump_llvm_ir_unoptimized( + &self, + contract_path: &str, + module: &inkwell::module::Module, + ) -> anyhow::Result<()> { + let llvm_code = module.print_to_string().to_string(); + + let mut file_path = self.output_directory.to_owned(); + let full_file_name = Self::full_file_name(contract_path, Some("unoptimized"), IRType::LLVM); + file_path.push(full_file_name); + std::fs::write(file_path, llvm_code)?; + + Ok(()) + } + + /// + /// Dumps the optimized LLVM IR. + /// + pub fn dump_llvm_ir_optimized( + &self, + contract_path: &str, + module: &inkwell::module::Module, + ) -> anyhow::Result<()> { + let llvm_code = module.print_to_string().to_string(); + + let mut file_path = self.output_directory.to_owned(); + let full_file_name = Self::full_file_name(contract_path, Some("optimized"), IRType::LLVM); + file_path.push(full_file_name); + std::fs::write(file_path, llvm_code)?; + + Ok(()) + } + + /// + /// Dumps the assembly. + /// + pub fn dump_assembly(&self, contract_path: &str, code: &str) -> anyhow::Result<()> { + let mut file_path = self.output_directory.to_owned(); + let full_file_name = Self::full_file_name(contract_path, None, IRType::Assembly); + file_path.push(full_file_name); + std::fs::write(file_path, code)?; + + Ok(()) + } + + /// + /// Creates a full file name, given the contract full path, suffix, and extension. + /// + fn full_file_name(contract_path: &str, suffix: Option<&str>, ir_type: IRType) -> String { + let mut full_file_name = contract_path.replace('/', "_").replace(':', "."); + if let Some(suffix) = suffix { + full_file_name.push('.'); + full_file_name.push_str(suffix); + } + full_file_name.push('.'); + full_file_name.push_str(ir_type.file_extension()); + full_file_name + } +} diff --git a/crates/llvm-context/src/eravm/const.rs b/crates/llvm-context/src/eravm/const.rs new file mode 100644 index 0000000..77303dd --- /dev/null +++ b/crates/llvm-context/src/eravm/const.rs @@ -0,0 +1,72 @@ +//! +//! The LLVM context constants. +//! + +/// The LLVM framework version. +pub const LLVM_VERSION: semver::Version = semver::Version::new(15, 0, 4); + +/// The EraVM version. +pub const ZKEVM_VERSION: semver::Version = semver::Version::new(1, 3, 2); + +/// The heap memory pointer pointer global variable name. +pub static GLOBAL_HEAP_MEMORY_POINTER: &str = "memory_pointer"; + +/// The calldata pointer global variable name. +pub static GLOBAL_CALLDATA_POINTER: &str = "ptr_calldata"; + +/// The calldata size global variable name. +pub static GLOBAL_CALLDATA_SIZE: &str = "calldatasize"; + +/// The return data pointer global variable name. +pub static GLOBAL_RETURN_DATA_POINTER: &str = "ptr_return_data"; + +/// The return data size pointer global variable name. +pub static GLOBAL_RETURN_DATA_SIZE: &str = "returndatasize"; + +/// The call flags global variable name. +pub static GLOBAL_CALL_FLAGS: &str = "call_flags"; + +/// The extra ABI data global variable name. +pub static GLOBAL_EXTRA_ABI_DATA: &str = "extra_abi_data"; + +/// The active pointer global variable name. +pub static GLOBAL_ACTIVE_POINTER: &str = "ptr_active"; + +/// The constant array global variable name prefix. +pub static GLOBAL_CONST_ARRAY_PREFIX: &str = "const_array_"; + +/// The global verbatim getter identifier prefix. +pub static GLOBAL_VERBATIM_GETTER_PREFIX: &str = "get_global::"; + +/// The external call data offset in the auxiliary heap. +pub const HEAP_AUX_OFFSET_EXTERNAL_CALL: u64 = 0; + +/// The constructor return data offset in the auxiliary heap. +pub const HEAP_AUX_OFFSET_CONSTRUCTOR_RETURN_DATA: u64 = + 8 * (era_compiler_common::BYTE_LENGTH_FIELD as u64); + +/// The number of the extra ABI data arguments. +pub const EXTRA_ABI_DATA_SIZE: usize = 0; + +/// The `create` method deployer signature. +pub static DEPLOYER_SIGNATURE_CREATE: &str = "create(bytes32,bytes32,bytes)"; + +/// The `create2` method deployer signature. +pub static DEPLOYER_SIGNATURE_CREATE2: &str = "create2(bytes32,bytes32,bytes)"; + +/// The absence of system call bit. +pub const NO_SYSTEM_CALL_BIT: bool = false; + +/// The system call bit. +pub const SYSTEM_CALL_BIT: bool = true; + +/// +/// The deployer call header size that consists of: +/// - selector (4 bytes) +/// - salt (32 bytes) +/// - bytecode hash (32 bytes) +/// - constructor arguments offset (32 bytes) +/// - constructor arguments length (32 bytes) +/// +pub const DEPLOYER_CALL_HEADER_SIZE: usize = + era_compiler_common::BYTE_LENGTH_X32 + (era_compiler_common::BYTE_LENGTH_FIELD * 4); diff --git a/crates/llvm-context/src/eravm/context/address_space.rs b/crates/llvm-context/src/eravm/context/address_space.rs new file mode 100644 index 0000000..20d4266 --- /dev/null +++ b/crates/llvm-context/src/eravm/context/address_space.rs @@ -0,0 +1,38 @@ +//! +//! The address space aliases. +//! + +/// +/// The address space aliases. +/// +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum AddressSpace { + /// The stack memory. + Stack, + /// The heap memory. + Heap, + /// The auxiliary heap memory. + HeapAuxiliary, + /// The generic memory page. + Generic, + /// The code area. + Code, + /// The storage. + Storage, + /// The transient storage. + TransientStorage, +} + +impl From for inkwell::AddressSpace { + fn from(value: AddressSpace) -> Self { + match value { + AddressSpace::Stack => Self::from(0), + AddressSpace::Heap => Self::from(1), + AddressSpace::HeapAuxiliary => Self::from(2), + AddressSpace::Generic => Self::from(3), + AddressSpace::Code => Self::from(4), + AddressSpace::Storage => Self::from(5), + AddressSpace::TransientStorage => Self::from(6), + } + } +} diff --git a/crates/llvm-context/src/eravm/context/argument.rs b/crates/llvm-context/src/eravm/context/argument.rs new file mode 100644 index 0000000..5f0b966 --- /dev/null +++ b/crates/llvm-context/src/eravm/context/argument.rs @@ -0,0 +1,76 @@ +//! +//! The LLVM argument with metadata. +//! + +/// +/// The LLVM argument with metadata. +/// +#[derive(Debug, Clone)] +pub struct Argument<'ctx> { + /// The actual LLVM operand. + pub value: inkwell::values::BasicValueEnum<'ctx>, + /// The original AST value. Used mostly for string literals. + pub original: Option, + /// The preserved constant value, if available. + pub constant: Option, +} + +impl<'ctx> Argument<'ctx> { + /// The calldata offset argument index. + pub const ARGUMENT_INDEX_CALLDATA_OFFSET: usize = 0; + + /// The calldata length argument index. + pub const ARGUMENT_INDEX_CALLDATA_LENGTH: usize = 1; + + /// + /// A shortcut constructor. + /// + pub fn new(value: inkwell::values::BasicValueEnum<'ctx>) -> Self { + Self { + value, + original: None, + constant: None, + } + } + + /// + /// A shortcut constructor. + /// + pub fn new_with_original( + value: inkwell::values::BasicValueEnum<'ctx>, + original: String, + ) -> Self { + Self { + value, + original: Some(original), + constant: None, + } + } + + /// + /// A shortcut constructor. + /// + pub fn new_with_constant( + value: inkwell::values::BasicValueEnum<'ctx>, + constant: num::BigUint, + ) -> Self { + Self { + value, + original: None, + constant: Some(constant), + } + } + + /// + /// Returns the inner LLVM value. + /// + pub fn to_llvm(&self) -> inkwell::values::BasicValueEnum<'ctx> { + self.value + } +} + +impl<'ctx> From> for Argument<'ctx> { + fn from(value: inkwell::values::BasicValueEnum<'ctx>) -> Self { + Self::new(value) + } +} diff --git a/crates/llvm-context/src/eravm/context/attribute.rs b/crates/llvm-context/src/eravm/context/attribute.rs new file mode 100644 index 0000000..6a94503 --- /dev/null +++ b/crates/llvm-context/src/eravm/context/attribute.rs @@ -0,0 +1,127 @@ +//! The LLVM attribute. + +use serde::Deserialize; +use serde::Serialize; + +/// The LLVM attribute. +/// +/// In order to check the real order in a new major version of LLVM, find the `Attribute.inc` file +/// inside of the LLVM build directory. This order is actually generated during the building. +/// +/// FIXME: Generate this in build.rs? +#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum Attribute { + /// Unused (attributes start at 1). + Unused = 0, + AllocAlign = 1, + AllocatedPointer = 2, + AlwaysInline = 3, + Builtin = 4, + Cold = 5, + Convergent = 6, + DisableSanitizerInstrumentation = 7, + FnRetThunkExtern = 8, + Hot = 9, + ImmArg = 10, + InReg = 11, + InlineHint = 12, + JumpTable = 13, + MinSize = 14, + MustProgress = 15, + Naked = 16, + Nest = 17, + NoAlias = 18, + NoBuiltin = 19, + NoCallback = 20, + NoCapture = 21, + NoCfCheck = 22, + NoDuplicate = 23, + NoFree = 24, + NoImplicitFloat = 25, + NoInline = 26, + NoMerge = 27, + NoProfile = 28, + NoRecurse = 29, + NoRedZone = 30, + NoReturn = 31, + NoSanitizeBounds = 32, + NoSanitizeCoverage = 33, + NoSync = 34, + NoUndef = 35, + NoUnwind = 36, + NonLazyBind = 37, + NonNull = 38, + NullPointerIsValid = 39, + OptForFuzzing = 40, + OptimizeForSize = 41, + OptimizeNone = 42, + PresplitCoroutine = 43, + ReadNone = 44, + ReadOnly = 45, + Returned = 46, + ReturnsTwice = 47, + SExt = 48, + SafeStack = 49, + SanitizeAddress = 50, + SanitizeHWAddress = 51, + SanitizeMemTag = 52, + SanitizeMemory = 53, + SanitizeThread = 54, + ShadowCallStack = 55, + SkipProfile = 56, + Speculatable = 57, + SpeculativeLoadHardening = 58, + StackProtect = 59, + StackProtectReq = 60, + StackProtectStrong = 61, + StrictFP = 62, + SwiftAsync = 63, + SwiftError = 64, + SwiftSelf = 65, + WillReturn = 66, + WriteOnly = 67, + ZExt = 68, + // FirstTypeAttr = 69, + ByRef = 69, + ByVal = 70, + ElementType = 71, + InAlloca = 72, + Preallocated = 73, + StructRet = 74, + // LastTypeAttr = 74, + // FirstIntAttr = 75, + Alignment = 75, + AllocKind = 76, + AllocSize = 77, + Dereferenceable = 78, + DereferenceableOrNull = 79, + Memory = 80, + StackAlignment = 81, + UWTable = 82, + VScaleRange = 83, + // LastIntAttr = 83, +} + +impl TryFrom<&str> for Attribute { + type Error = String; + + fn try_from(value: &str) -> Result { + match value { + "AlwaysInline" => Ok(Attribute::AlwaysInline), + "Cold" => Ok(Attribute::Cold), + "Hot" => Ok(Attribute::Hot), + "MinSize" => Ok(Attribute::MinSize), + "OptimizeForSize" => Ok(Attribute::OptimizeForSize), + "NoInline" => Ok(Attribute::NoInline), + "WillReturn" => Ok(Attribute::WillReturn), + "WriteOnly" => Ok(Attribute::WriteOnly), + "ReadNone" => Ok(Attribute::ReadNone), + "ReadOnly" => Ok(Attribute::ReadOnly), + "NoReturn" => Ok(Attribute::NoReturn), + // FIXME: Not in Attributes.inc + //"InaccessibleMemOnly" => Ok(Attribute::InaccessibleMemOnly), + "MustProgress" => Ok(Attribute::MustProgress), + _ => Err(value.to_owned()), + } + } +} diff --git a/crates/llvm-context/src/eravm/context/build.rs b/crates/llvm-context/src/eravm/context/build.rs new file mode 100644 index 0000000..9d55bcf --- /dev/null +++ b/crates/llvm-context/src/eravm/context/build.rs @@ -0,0 +1,45 @@ +//! +//! The LLVM module build. +//! + +use std::collections::BTreeMap; + +use serde::Deserialize; +use serde::Serialize; + +/// +/// The LLVM module build. +/// +#[derive(Debug, Serialize, Deserialize)] +pub struct Build { + /// The EraVM text assembly. + pub assembly_text: String, + /// The metadata hash. + pub metadata_hash: Option<[u8; era_compiler_common::BYTE_LENGTH_FIELD]>, + /// The EraVM binary bytecode. + pub bytecode: Vec, + /// The EraVM bytecode hash. + pub bytecode_hash: String, + /// The hash-to-full-path mapping of the contract factory dependencies. + pub factory_dependencies: BTreeMap, +} + +impl Build { + /// + /// A shortcut constructor. + /// + pub fn new( + assembly_text: String, + metadata_hash: Option<[u8; era_compiler_common::BYTE_LENGTH_FIELD]>, + bytecode: Vec, + bytecode_hash: String, + ) -> Self { + Self { + assembly_text, + metadata_hash, + bytecode, + bytecode_hash, + factory_dependencies: BTreeMap::new(), + } + } +} diff --git a/crates/llvm-context/src/eravm/context/code_type.rs b/crates/llvm-context/src/eravm/context/code_type.rs new file mode 100644 index 0000000..a2ef301 --- /dev/null +++ b/crates/llvm-context/src/eravm/context/code_type.rs @@ -0,0 +1,26 @@ +//! +//! The contract code types. +//! + +/// +/// The contract code types (deploy and runtime). +/// +/// They do not represent any entities in the final bytecode, but this separation is always present +/// in the IRs used for translation to the EVM bytecode. +/// +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum CodeType { + /// The deploy code. + Deploy, + /// The runtime code. + Runtime, +} + +impl std::fmt::Display for CodeType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Deploy => write!(f, "deploy"), + Self::Runtime => write!(f, "runtime"), + } + } +} diff --git a/crates/llvm-context/src/eravm/context/debug_info.rs b/crates/llvm-context/src/eravm/context/debug_info.rs new file mode 100644 index 0000000..de347e1 --- /dev/null +++ b/crates/llvm-context/src/eravm/context/debug_info.rs @@ -0,0 +1,109 @@ +//! +//! The LLVM debug information. +//! + +use inkwell::debug_info::AsDIScope; +use num::Zero; + +/// +/// The LLVM debug information. +/// +pub struct DebugInfo<'ctx> { + /// The compile unit. + compile_unit: inkwell::debug_info::DICompileUnit<'ctx>, + /// The debug info builder. + builder: inkwell::debug_info::DebugInfoBuilder<'ctx>, +} + +impl<'ctx> DebugInfo<'ctx> { + /// + /// A shortcut constructor. + /// + pub fn new(module: &inkwell::module::Module<'ctx>) -> Self { + let (builder, compile_unit) = module.create_debug_info_builder( + true, + inkwell::debug_info::DWARFSourceLanguage::C, + module.get_name().to_string_lossy().as_ref(), + "", + "", + false, + "", + 0, + "", + inkwell::debug_info::DWARFEmissionKind::Full, + 0, + false, + false, + "", + "", + ); + + Self { + compile_unit, + builder, + } + } + + /// + /// Creates a function info. + /// + pub fn create_function( + &self, + name: &str, + ) -> anyhow::Result> { + let subroutine_type = self.builder.create_subroutine_type( + self.compile_unit.get_file(), + Some(self.create_type(era_compiler_common::BIT_LENGTH_FIELD)?), + &[], + inkwell::debug_info::DIFlags::zero(), + ); + + let function = self.builder.create_function( + self.compile_unit.get_file().as_debug_info_scope(), + name, + None, + self.compile_unit.get_file(), + 42, + subroutine_type, + true, + false, + 1, + inkwell::debug_info::DIFlags::zero(), + false, + ); + + self.builder.create_lexical_block( + function.as_debug_info_scope(), + self.compile_unit.get_file(), + 1, + 1, + ); + + Ok(function) + } + + /// + /// Creates a primitive type info. + /// + pub fn create_type( + &self, + bit_length: usize, + ) -> anyhow::Result> { + self.builder + .create_basic_type( + "U256", + bit_length as u64, + 0, + inkwell::debug_info::DIFlags::zero(), + ) + .map(|basic_type| basic_type.as_type()) + .map_err(|error| anyhow::anyhow!("Debug info error: {}", error)) + } + + /// + /// Finalizes the builder. + /// + pub fn finalize(&self) { + self.builder.finalize(); + } +} diff --git a/crates/llvm-context/src/eravm/context/evmla_data.rs b/crates/llvm-context/src/eravm/context/evmla_data.rs new file mode 100644 index 0000000..79ff666 --- /dev/null +++ b/crates/llvm-context/src/eravm/context/evmla_data.rs @@ -0,0 +1,34 @@ +//! +//! The LLVM IR generator EVM legacy assembly data. +//! + +use crate::eravm::context::argument::Argument; + +/// +/// The LLVM IR generator EVM legacy assembly data. +/// +/// Describes some data that is only relevant to the EVM legacy assembly. +/// +#[derive(Debug, Clone)] +pub struct EVMLAData<'ctx> { + /// The Solidity compiler version. + /// Some instruction behave differenly depending on the version. + pub version: semver::Version, + /// The static stack allocated for the current function. + pub stack: Vec>, +} + +impl<'ctx> EVMLAData<'ctx> { + /// The default stack size. + pub const DEFAULT_STACK_SIZE: usize = 64; + + /// + /// A shortcut constructor. + /// + pub fn new(version: semver::Version) -> Self { + Self { + version, + stack: Vec::with_capacity(Self::DEFAULT_STACK_SIZE), + } + } +} diff --git a/crates/llvm-context/src/eravm/context/function/block/evmla_data/key.rs b/crates/llvm-context/src/eravm/context/function/block/evmla_data/key.rs new file mode 100644 index 0000000..4f9aa1f --- /dev/null +++ b/crates/llvm-context/src/eravm/context/function/block/evmla_data/key.rs @@ -0,0 +1,41 @@ +//! +//! The LLVM IR generator function block key. +//! + +use crate::eravm::context::code_type::CodeType; + +/// +/// The LLVM IR generator function block key. +/// +/// Is only relevant to the EVM legacy assembly. +/// +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Key { + /// The block code type. + pub code_type: CodeType, + /// The block tag. + pub tag: num::BigUint, +} + +impl Key { + /// + /// A shortcut constructor. + /// + pub fn new(code_type: CodeType, tag: num::BigUint) -> Self { + Self { code_type, tag } + } +} + +impl std::fmt::Display for Key { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}_{}", + match self.code_type { + CodeType::Deploy => "dt", + CodeType::Runtime => "rt", + }, + self.tag + ) + } +} diff --git a/crates/llvm-context/src/eravm/context/function/block/evmla_data/mod.rs b/crates/llvm-context/src/eravm/context/function/block/evmla_data/mod.rs new file mode 100644 index 0000000..8c5f9a7 --- /dev/null +++ b/crates/llvm-context/src/eravm/context/function/block/evmla_data/mod.rs @@ -0,0 +1,25 @@ +//! +//! The LLVM function block EVM legacy assembly data. +//! + +pub mod key; + +/// +/// The LLVM function block EVM legacy assembly data. +/// +/// Describes some data that is only relevant to the EVM legacy assembly. +/// +#[derive(Debug, Clone)] +pub struct EVMLAData { + /// The initial hashes of the allowed stack states. + pub stack_hashes: Vec, +} + +impl EVMLAData { + /// + /// A shortcut constructor. + /// + pub fn new(stack_hashes: Vec) -> Self { + Self { stack_hashes } + } +} diff --git a/crates/llvm-context/src/eravm/context/function/block/mod.rs b/crates/llvm-context/src/eravm/context/function/block/mod.rs new file mode 100644 index 0000000..ac7527c --- /dev/null +++ b/crates/llvm-context/src/eravm/context/function/block/mod.rs @@ -0,0 +1,68 @@ +//! +//! The LLVM IR generator function block. +//! + +pub mod evmla_data; + +use self::evmla_data::EVMLAData; + +/// +/// The LLVM IR generator function block. +/// +#[derive(Debug, Clone)] +pub struct Block<'ctx> { + /// The inner block. + inner: inkwell::basic_block::BasicBlock<'ctx>, + /// The EVM legacy assembly compiler data. + evmla_data: Option, +} + +impl<'ctx> Block<'ctx> { + /// + /// A shortcut constructor. + /// + pub fn new(inner: inkwell::basic_block::BasicBlock<'ctx>) -> Self { + Self { + inner, + evmla_data: None, + } + } + + /// + /// Sets the EVM legacy assembly data. + /// + pub fn set_evmla_data(&mut self, data: EVMLAData) { + self.evmla_data = Some(data); + } + + /// + /// The LLVM object reference. + /// + pub fn inner(&self) -> inkwell::basic_block::BasicBlock<'ctx> { + self.inner + } + + /// + /// Returns the EVM data reference. + /// + /// # Panics + /// If the EVM data has not been initialized. + /// + pub fn evm(&self) -> &EVMLAData { + self.evmla_data + .as_ref() + .expect("The EVM data must have been initialized") + } + + /// + /// Returns the EVM data mutable reference. + /// + /// # Panics + /// If the EVM data has not been initialized. + /// + pub fn evm_mut(&mut self) -> &mut EVMLAData { + self.evmla_data + .as_mut() + .expect("The EVM data must have been initialized") + } +} diff --git a/crates/llvm-context/src/eravm/context/function/declaration.rs b/crates/llvm-context/src/eravm/context/function/declaration.rs new file mode 100644 index 0000000..0c49f39 --- /dev/null +++ b/crates/llvm-context/src/eravm/context/function/declaration.rs @@ -0,0 +1,26 @@ +//! +//! The LLVM function declaration. +//! + +/// +/// The LLVM function declaration. +/// +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Declaration<'ctx> { + /// The function type. + pub r#type: inkwell::types::FunctionType<'ctx>, + /// The function value. + pub value: inkwell::values::FunctionValue<'ctx>, +} + +impl<'ctx> Declaration<'ctx> { + /// + /// A shortcut constructor. + /// + pub fn new( + r#type: inkwell::types::FunctionType<'ctx>, + value: inkwell::values::FunctionValue<'ctx>, + ) -> Self { + Self { r#type, value } + } +} diff --git a/crates/llvm-context/src/eravm/context/function/evmla_data.rs b/crates/llvm-context/src/eravm/context/function/evmla_data.rs new file mode 100644 index 0000000..8378c28 --- /dev/null +++ b/crates/llvm-context/src/eravm/context/function/evmla_data.rs @@ -0,0 +1,86 @@ +//! +//! The LLVM function EVM legacy assembly data. +//! + +use std::collections::BTreeMap; + +use crate::eravm::context::function::block::evmla_data::key::Key as BlockKey; +use crate::eravm::context::function::block::Block; + +/// +/// The LLVM function EVM legacy assembly data. +/// +/// Describes some data that is only relevant to the EVM legacy assembly. +/// +#[derive(Debug)] +pub struct EVMLAData<'ctx> { + /// The ordinary blocks with numeric tags. + /// Is only used by the Solidity EVM compiler. + pub blocks: BTreeMap>>, + /// The function stack size. + pub stack_size: usize, +} + +impl<'ctx> EVMLAData<'ctx> { + /// + /// A shortcut constructor. + /// + pub fn new(stack_size: usize) -> Self { + Self { + blocks: BTreeMap::new(), + stack_size, + } + } + + /// + /// Inserts a function block. + /// + pub fn insert_block(&mut self, key: BlockKey, block: Block<'ctx>) { + if let Some(blocks) = self.blocks.get_mut(&key) { + blocks.push(block); + } else { + self.blocks.insert(key, vec![block]); + } + } + + /// + /// Returns the block with the specified tag and initial stack pattern. + /// + /// If there is only one block, it is returned unconditionally. + /// + pub fn find_block( + &self, + key: &BlockKey, + stack_hash: &md5::Digest, + ) -> anyhow::Result> { + if self + .blocks + .get(key) + .ok_or_else(|| anyhow::anyhow!("Undeclared function block {}", key))? + .len() + == 1 + { + return self + .blocks + .get(key) + .ok_or_else(|| anyhow::anyhow!("Undeclared function block {}", key))? + .first() + .cloned() + .ok_or_else(|| anyhow::anyhow!("Undeclared function block {}", key)); + } + + self.blocks + .get(key) + .ok_or_else(|| anyhow::anyhow!("Undeclared function block {}", key))? + .iter() + .find(|block| { + block + .evm() + .stack_hashes + .iter() + .any(|hash| hash == stack_hash) + }) + .cloned() + .ok_or_else(|| anyhow::anyhow!("Undeclared function block {}", key)) + } +} diff --git a/crates/llvm-context/src/eravm/context/function/intrinsics.rs b/crates/llvm-context/src/eravm/context/function/intrinsics.rs new file mode 100644 index 0000000..46ef07c --- /dev/null +++ b/crates/llvm-context/src/eravm/context/function/intrinsics.rs @@ -0,0 +1,154 @@ +//! +//! The LLVM intrinsic functions. +//! + +use inkwell::types::BasicType; + +use crate::eravm::context::address_space::AddressSpace; +use crate::eravm::context::function::declaration::Declaration as FunctionDeclaration; + +/// +/// The LLVM intrinsic functions, implemented in the LLVM back-end. +/// +/// Most of them are translated directly into bytecode instructions. +/// +#[derive(Debug)] +pub struct Intrinsics<'ctx> { + /// The trap. + pub trap: FunctionDeclaration<'ctx>, + /// The memory copy within the heap. + pub memory_copy: FunctionDeclaration<'ctx>, + /// The memory copy from a generic page. + pub memory_copy_from_generic: FunctionDeclaration<'ctx>, + /// Performs endianness swaps on i256 values + pub byte_swap: FunctionDeclaration<'ctx>, +} + +impl<'ctx> Intrinsics<'ctx> { + /// The corresponding intrinsic function name. + pub const FUNCTION_TRAP: &'static str = "llvm.trap"; + + /// The corresponding intrinsic function name. + pub const FUNCTION_MEMORY_COPY: &'static str = "llvm.memcpy.p1.p1.i256"; + + /// The corresponding intrinsic function name. + pub const FUNCTION_MEMORY_COPY_FROM_GENERIC: &'static str = "llvm.memcpy.p3.p1.i256"; + + /// The corresponding intrinsic function name. + pub const FUNCTION_BYTE_SWAP: &'static str = "llvm.bswap.i256"; + + /// + /// A shortcut constructor. + /// + pub fn new( + llvm: &'ctx inkwell::context::Context, + module: &inkwell::module::Module<'ctx>, + ) -> Self { + let void_type = llvm.void_type(); + let bool_type = llvm.bool_type(); + let byte_type = llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_BYTE as u32); + let field_type = llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32); + let _stack_field_pointer_type = field_type.ptr_type(AddressSpace::Stack.into()); + let heap_field_pointer_type = byte_type.ptr_type(AddressSpace::Heap.into()); + let generic_byte_pointer_type = byte_type.ptr_type(AddressSpace::Generic.into()); + + let trap = Self::declare( + llvm, + module, + Self::FUNCTION_TRAP, + void_type.fn_type(&[], false), + ); + let memory_copy = Self::declare( + llvm, + module, + Self::FUNCTION_MEMORY_COPY, + void_type.fn_type( + &[ + heap_field_pointer_type.as_basic_type_enum().into(), + heap_field_pointer_type.as_basic_type_enum().into(), + field_type.as_basic_type_enum().into(), + bool_type.as_basic_type_enum().into(), + ], + false, + ), + ); + let memory_copy_from_generic = Self::declare( + llvm, + module, + Self::FUNCTION_MEMORY_COPY_FROM_GENERIC, + void_type.fn_type( + &[ + heap_field_pointer_type.as_basic_type_enum().into(), + generic_byte_pointer_type.as_basic_type_enum().into(), + field_type.as_basic_type_enum().into(), + bool_type.as_basic_type_enum().into(), + ], + false, + ), + ); + let byte_swap = Self::declare( + llvm, + module, + Self::FUNCTION_BYTE_SWAP, + field_type.fn_type(&[field_type.as_basic_type_enum().into()], false), + ); + + Self { + trap, + memory_copy, + memory_copy_from_generic, + byte_swap, + } + } + + /// + /// Finds the specified LLVM intrinsic function in the target and returns its declaration. + /// + pub fn declare( + llvm: &'ctx inkwell::context::Context, + module: &inkwell::module::Module<'ctx>, + name: &str, + r#type: inkwell::types::FunctionType<'ctx>, + ) -> FunctionDeclaration<'ctx> { + let intrinsic = inkwell::intrinsics::Intrinsic::find(name) + .unwrap_or_else(|| panic!("Intrinsic function `{name}` does not exist")); + let argument_types = Self::argument_types(llvm, name); + let value = intrinsic + .get_declaration(module, argument_types.as_slice()) + .unwrap_or_else(|| panic!("Intrinsic function `{name}` declaration error")); + FunctionDeclaration::new(r#type, value) + } + + /// + /// Returns the LLVM types for selecting via the signature. + /// + pub fn argument_types( + llvm: &'ctx inkwell::context::Context, + name: &str, + ) -> Vec> { + let field_type = llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32); + + match name { + name if name == Self::FUNCTION_MEMORY_COPY => vec![ + field_type + .ptr_type(AddressSpace::Heap.into()) + .as_basic_type_enum(), + field_type + .ptr_type(AddressSpace::Heap.into()) + .as_basic_type_enum(), + field_type.as_basic_type_enum(), + ], + name if name == Self::FUNCTION_MEMORY_COPY_FROM_GENERIC => vec![ + field_type + .ptr_type(AddressSpace::Heap.into()) + .as_basic_type_enum(), + field_type + .ptr_type(AddressSpace::Generic.into()) + .as_basic_type_enum(), + field_type.as_basic_type_enum(), + ], + name if name == Self::FUNCTION_BYTE_SWAP => vec![field_type.as_basic_type_enum()], + _ => vec![], + } + } +} diff --git a/crates/llvm-context/src/eravm/context/function/llvm_runtime.rs b/crates/llvm-context/src/eravm/context/function/llvm_runtime.rs new file mode 100644 index 0000000..4cfd893 --- /dev/null +++ b/crates/llvm-context/src/eravm/context/function/llvm_runtime.rs @@ -0,0 +1,758 @@ +//! +//! The LLVM runtime functions. +//! + +use inkwell::types::BasicType; + +use crate::eravm::context::address_space::AddressSpace; +use crate::eravm::context::attribute::Attribute; +use crate::eravm::context::function::declaration::Declaration as FunctionDeclaration; +use crate::eravm::context::function::Function; +use crate::optimizer::Optimizer; + +/// +/// The runtime functions, implemented on the LLVM side. +/// +/// The functions are automatically linked to the LLVM implementations if the signatures match. +/// +#[derive(Debug)] +pub struct LLVMRuntime<'ctx> { + /// The LLVM personality function, used for exception handling. + pub personality: FunctionDeclaration<'ctx>, + /// The LLVM exception throwing function. + pub cxa_throw: FunctionDeclaration<'ctx>, + + /// The corresponding LLVM runtime function. + pub div: FunctionDeclaration<'ctx>, + /// The corresponding LLVM runtime function. + pub sdiv: FunctionDeclaration<'ctx>, + /// The corresponding LLVM runtime function. + pub r#mod: FunctionDeclaration<'ctx>, + /// The corresponding LLVM runtime function. + pub smod: FunctionDeclaration<'ctx>, + + /// The corresponding LLVM runtime function. + pub shl: FunctionDeclaration<'ctx>, + /// The corresponding LLVM runtime function. + pub shr: FunctionDeclaration<'ctx>, + /// The corresponding LLVM runtime function. + pub sar: FunctionDeclaration<'ctx>, + /// The corresponding LLVM runtime function. + pub byte: FunctionDeclaration<'ctx>, + + /// The corresponding LLVM runtime function. + pub add_mod: FunctionDeclaration<'ctx>, + /// The corresponding LLVM runtime function. + pub mul_mod: FunctionDeclaration<'ctx>, + /// The corresponding LLVM runtime function. + pub exp: FunctionDeclaration<'ctx>, + /// The corresponding LLVM runtime function. + pub sign_extend: FunctionDeclaration<'ctx>, + + /// The corresponding LLVM runtime function. + pub mstore8: FunctionDeclaration<'ctx>, + + /// The corresponding LLVM runtime function. + pub sha3: FunctionDeclaration<'ctx>, + + /// The corresponding LLVM runtime function. + pub system_request: FunctionDeclaration<'ctx>, + + /// The corresponding LLVM runtime function. + //pub far_call: FunctionDeclaration<'ctx>, + /// The corresponding LLVM runtime function. + pub far_call_byref: FunctionDeclaration<'ctx>, + + /// The corresponding LLVM runtime function. + pub static_call: FunctionDeclaration<'ctx>, + /// The corresponding LLVM runtime function. + pub static_call_byref: FunctionDeclaration<'ctx>, + + /// The corresponding LLVM runtime function. + pub delegate_call: FunctionDeclaration<'ctx>, + /// The corresponding LLVM runtime function. + pub delegate_call_byref: FunctionDeclaration<'ctx>, + + /// The corresponding LLVM runtime function. + pub mimic_call: FunctionDeclaration<'ctx>, + /// The corresponding LLVM runtime function. + pub mimic_call_byref: FunctionDeclaration<'ctx>, + + /// The corresponding LLVM runtime function. + pub r#return: FunctionDeclaration<'ctx>, + /// The corresponding LLVM runtime function. + pub revert: FunctionDeclaration<'ctx>, +} + +impl<'ctx> LLVMRuntime<'ctx> { + /// The LLVM personality function name. + pub const FUNCTION_PERSONALITY: &'static str = "__personality"; + + /// The LLVM exception throwing function name. + pub const FUNCTION_CXA_THROW: &'static str = "__cxa_throw"; + + /// The corresponding runtime function name. + pub const FUNCTION_DIV: &'static str = "__div"; + + /// The corresponding runtime function name. + pub const FUNCTION_SDIV: &'static str = "__sdiv"; + + /// The corresponding runtime function name. + pub const FUNCTION_MOD: &'static str = "__mod"; + + /// The corresponding runtime function name. + pub const FUNCTION_SMOD: &'static str = "__smod"; + + /// The corresponding runtime function name. + pub const FUNCTION_SHL: &'static str = "__shl"; + + /// The corresponding runtime function name. + pub const FUNCTION_SHR: &'static str = "__shr"; + + /// The corresponding runtime function name. + pub const FUNCTION_SAR: &'static str = "__sar"; + + /// The corresponding runtime function name. + pub const FUNCTION_BYTE: &'static str = "__byte"; + + /// The corresponding runtime function name. + pub const FUNCTION_ADDMOD: &'static str = "__addmod"; + + /// The corresponding runtime function name. + pub const FUNCTION_MULMOD: &'static str = "__mulmod"; + + /// The corresponding runtime function name. + pub const FUNCTION_EXP: &'static str = "__exp"; + + /// The corresponding runtime function name. + pub const FUNCTION_SIGNEXTEND: &'static str = "__signextend"; + + /// The corresponding runtime function name. + pub const FUNCTION_MSTORE8: &'static str = "__mstore8"; + + /// The corresponding runtime function name. + pub const FUNCTION_SHA3: &'static str = "__sha3"; + + /// The corresponding runtime function name. + pub const FUNCTION_SYSTEM_REQUEST: &'static str = "__system_request"; + + /// The corresponding runtime function name. + pub const FUNCTION_FARCALL: &'static str = "__farcall"; + + /// The corresponding runtime function name. + pub const FUNCTION_STATICCALL: &'static str = "__staticcall"; + + /// The corresponding runtime function name. + pub const FUNCTION_DELEGATECALL: &'static str = "__delegatecall"; + + /// The corresponding runtime function name. + pub const FUNCTION_MIMICCALL: &'static str = "__mimiccall"; + + /// The corresponding runtime function name. + pub const FUNCTION_FARCALL_BYREF: &'static str = "__farcall_byref"; + + /// The corresponding runtime function name. + pub const FUNCTION_STATICCALL_BYREF: &'static str = "__staticcall_byref"; + + /// The corresponding runtime function name. + pub const FUNCTION_DELEGATECALL_BYREF: &'static str = "__delegatecall_byref"; + + /// The corresponding runtime function name. + pub const FUNCTION_MIMICCALL_BYREF: &'static str = "__mimiccall_byref"; + + /// The corresponding runtime function name. + pub const FUNCTION_RETURN: &'static str = "__return"; + + /// The corresponding runtime function name. + pub const FUNCTION_REVERT: &'static str = "__revert"; + + /// + /// A shortcut constructor. + /// + pub fn new( + llvm: &'ctx inkwell::context::Context, + module: &inkwell::module::Module<'ctx>, + optimizer: &Optimizer, + ) -> Self { + let personality = Self::declare( + module, + Self::FUNCTION_PERSONALITY, + llvm.i32_type().fn_type(&[], false), + None, + ); + + let cxa_throw = Self::declare( + module, + Self::FUNCTION_CXA_THROW, + llvm.void_type().fn_type( + vec![ + llvm.i8_type() + .ptr_type(AddressSpace::Stack.into()) + .as_basic_type_enum() + .into(); + 3 + ] + .as_slice(), + false, + ), + Some(inkwell::module::Linkage::External), + ); + Function::set_cxa_throw_attributes(llvm, cxa_throw); + + let div = Self::declare( + module, + Self::FUNCTION_DIV, + llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32) + .fn_type( + vec![ + llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32) + .as_basic_type_enum() + .into(); + 2 + ] + .as_slice(), + false, + ), + Some(inkwell::module::Linkage::External), + ); + Function::set_default_attributes(llvm, div, optimizer); + Function::set_pure_function_attributes(llvm, div); + + let r#mod = Self::declare( + module, + Self::FUNCTION_MOD, + llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32) + .fn_type( + vec![ + llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32) + .as_basic_type_enum() + .into(); + 2 + ] + .as_slice(), + false, + ), + Some(inkwell::module::Linkage::External), + ); + Function::set_default_attributes(llvm, r#mod, optimizer); + Function::set_pure_function_attributes(llvm, r#mod); + + let sdiv = Self::declare( + module, + Self::FUNCTION_SDIV, + llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32) + .fn_type( + vec![ + llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32) + .as_basic_type_enum() + .into(); + 2 + ] + .as_slice(), + false, + ), + Some(inkwell::module::Linkage::External), + ); + Function::set_default_attributes(llvm, sdiv, optimizer); + Function::set_pure_function_attributes(llvm, sdiv); + + let smod = Self::declare( + module, + Self::FUNCTION_SMOD, + llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32) + .fn_type( + vec![ + llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32) + .as_basic_type_enum() + .into(); + 2 + ] + .as_slice(), + false, + ), + Some(inkwell::module::Linkage::External), + ); + Function::set_default_attributes(llvm, smod, optimizer); + Function::set_pure_function_attributes(llvm, smod); + + let shl = Self::declare( + module, + Self::FUNCTION_SHL, + llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32) + .fn_type( + vec![ + llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32) + .as_basic_type_enum() + .into(); + 2 + ] + .as_slice(), + false, + ), + Some(inkwell::module::Linkage::External), + ); + Function::set_default_attributes(llvm, shl, optimizer); + Function::set_pure_function_attributes(llvm, shl); + + let shr = Self::declare( + module, + Self::FUNCTION_SHR, + llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32) + .fn_type( + vec![ + llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32) + .as_basic_type_enum() + .into(); + 2 + ] + .as_slice(), + false, + ), + Some(inkwell::module::Linkage::External), + ); + Function::set_default_attributes(llvm, shr, optimizer); + Function::set_pure_function_attributes(llvm, shr); + + let sar = Self::declare( + module, + Self::FUNCTION_SAR, + llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32) + .fn_type( + vec![ + llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32) + .as_basic_type_enum() + .into(); + 2 + ] + .as_slice(), + false, + ), + Some(inkwell::module::Linkage::External), + ); + Function::set_default_attributes(llvm, sar, optimizer); + Function::set_pure_function_attributes(llvm, sar); + + let byte = Self::declare( + module, + Self::FUNCTION_BYTE, + llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32) + .fn_type( + vec![ + llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32) + .as_basic_type_enum() + .into(); + 2 + ] + .as_slice(), + false, + ), + Some(inkwell::module::Linkage::External), + ); + Function::set_default_attributes(llvm, byte, optimizer); + Function::set_pure_function_attributes(llvm, byte); + + let add_mod = Self::declare( + module, + Self::FUNCTION_ADDMOD, + llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32) + .fn_type( + vec![ + llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32) + .as_basic_type_enum() + .into(); + 3 + ] + .as_slice(), + false, + ), + Some(inkwell::module::Linkage::External), + ); + Function::set_default_attributes(llvm, add_mod, optimizer); + Function::set_pure_function_attributes(llvm, add_mod); + + let mul_mod = Self::declare( + module, + Self::FUNCTION_MULMOD, + llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32) + .fn_type( + vec![ + llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32) + .as_basic_type_enum() + .into(); + 3 + ] + .as_slice(), + false, + ), + Some(inkwell::module::Linkage::External), + ); + Function::set_default_attributes(llvm, mul_mod, optimizer); + Function::set_pure_function_attributes(llvm, mul_mod); + + let exp = Self::declare( + module, + Self::FUNCTION_EXP, + llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32) + .fn_type( + vec![ + llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32) + .as_basic_type_enum() + .into(); + 2 + ] + .as_slice(), + false, + ), + Some(inkwell::module::Linkage::External), + ); + Function::set_default_attributes(llvm, exp, optimizer); + Function::set_pure_function_attributes(llvm, exp); + + let sign_extend = FunctionDeclaration::new( + llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32) + .fn_type( + vec![ + llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32) + .as_basic_type_enum() + .into(); + 2 + ] + .as_slice(), + false, + ), + module + .get_function(Self::FUNCTION_SIGNEXTEND) + .expect("should be declared in stdlib"), + ); + Function::set_default_attributes(llvm, sign_extend, optimizer); + Function::set_pure_function_attributes(llvm, sign_extend); + + let mstore8 = Self::declare( + module, + Self::FUNCTION_MSTORE8, + llvm.void_type().fn_type( + vec![ + llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_BYTE as u32) + .ptr_type(AddressSpace::Heap.into()) + .as_basic_type_enum() + .into(), + llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32) + .as_basic_type_enum() + .into(), + ] + .as_slice(), + false, + ), + Some(inkwell::module::Linkage::External), + ); + Function::set_default_attributes(llvm, mstore8, optimizer); + Function::set_attributes( + llvm, + mstore8, + vec![ + Attribute::MustProgress, + Attribute::NoUnwind, + Attribute::WillReturn, + ], + false, + ); + + let sha3 = Self::declare( + module, + Self::FUNCTION_SHA3, + llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32) + .fn_type( + vec![ + llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_BYTE as u32) + .ptr_type(AddressSpace::Heap.into()) + .as_basic_type_enum() + .into(), + llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32) + .as_basic_type_enum() + .into(), + llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_BOOLEAN as u32) + .as_basic_type_enum() + .into(), + ] + .as_slice(), + false, + ), + Some(inkwell::module::Linkage::External), + ); + Function::set_default_attributes(llvm, sha3, optimizer); + Function::set_attributes( + llvm, + sha3, + //vec![Attribute::ArgMemOnly, Attribute::ReadOnly], + vec![], + false, + ); + + let system_request = Self::declare( + module, + Self::FUNCTION_SYSTEM_REQUEST, + llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32) + .fn_type( + vec![ + llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32) + .as_basic_type_enum() + .into(), + llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32) + .as_basic_type_enum() + .into(), + llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32) + .as_basic_type_enum() + .into(), + llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32) + .ptr_type(AddressSpace::Stack.into()) + .as_basic_type_enum() + .into(), + ] + .as_slice(), + false, + ), + Some(inkwell::module::Linkage::External), + ); + Function::set_default_attributes(llvm, system_request, optimizer); + Function::set_attributes( + llvm, + system_request, + //vec![Attribute::ArgMemOnly, Attribute::ReadOnly], + vec![], + false, + ); + + let external_call_arguments: Vec = vec![ + llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32) + .as_basic_type_enum() + .into(); + crate::eravm::context::function::runtime::entry::Entry::MANDATORY_ARGUMENTS_COUNT + + crate::eravm::EXTRA_ABI_DATA_SIZE + ]; + let mut mimic_call_arguments = external_call_arguments.clone(); + mimic_call_arguments.push( + llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32) + .as_basic_type_enum() + .into(), + ); + + let mut external_call_arguments_by_ref: Vec = vec![ + llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_BYTE as u32) + .ptr_type(AddressSpace::Generic.into()) + .as_basic_type_enum() + .into(), + llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32) + .as_basic_type_enum() + .into(), + ]; + external_call_arguments_by_ref.extend::>(vec![ + llvm.custom_width_int_type( + era_compiler_common::BIT_LENGTH_FIELD as u32 + ) + .as_basic_type_enum() + .into(); + crate::eravm::EXTRA_ABI_DATA_SIZE + ]); + let mut mimic_call_arguments_by_ref = external_call_arguments_by_ref.clone(); + mimic_call_arguments_by_ref.push( + llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32) + .as_basic_type_enum() + .into(), + ); + + let external_call_result_type = llvm + .struct_type( + &[ + llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_BYTE as u32) + .ptr_type(AddressSpace::Generic.into()) + .as_basic_type_enum(), + llvm.bool_type().as_basic_type_enum(), + ], + false, + ) + .as_basic_type_enum(); + + //let far_call = Self::declare( + // module, + // Self::FUNCTION_FARCALL, + // external_call_result_type.fn_type(external_call_arguments.as_slice(), false), + // Some(inkwell::module::Linkage::External), + //); + //Function::set_default_attributes(llvm, far_call, optimizer); + let static_call = Self::declare( + module, + Self::FUNCTION_STATICCALL, + external_call_result_type.fn_type(external_call_arguments.as_slice(), false), + Some(inkwell::module::Linkage::External), + ); + Function::set_default_attributes(llvm, static_call, optimizer); + let delegate_call = Self::declare( + module, + Self::FUNCTION_DELEGATECALL, + external_call_result_type.fn_type(external_call_arguments.as_slice(), false), + Some(inkwell::module::Linkage::External), + ); + Function::set_default_attributes(llvm, delegate_call, optimizer); + let mimic_call = Self::declare( + module, + Self::FUNCTION_MIMICCALL, + external_call_result_type.fn_type(mimic_call_arguments.as_slice(), false), + Some(inkwell::module::Linkage::External), + ); + Function::set_default_attributes(llvm, mimic_call, optimizer); + + let far_call_byref = Self::declare( + module, + Self::FUNCTION_FARCALL_BYREF, + external_call_result_type.fn_type(external_call_arguments_by_ref.as_slice(), false), + Some(inkwell::module::Linkage::External), + ); + Function::set_default_attributes(llvm, far_call_byref, optimizer); + let static_call_byref = Self::declare( + module, + Self::FUNCTION_STATICCALL_BYREF, + external_call_result_type.fn_type(external_call_arguments_by_ref.as_slice(), false), + Some(inkwell::module::Linkage::External), + ); + Function::set_default_attributes(llvm, static_call_byref, optimizer); + let delegate_call_byref = Self::declare( + module, + Self::FUNCTION_DELEGATECALL_BYREF, + external_call_result_type.fn_type(external_call_arguments_by_ref.as_slice(), false), + Some(inkwell::module::Linkage::External), + ); + Function::set_default_attributes(llvm, delegate_call_byref, optimizer); + let mimic_call_byref = Self::declare( + module, + Self::FUNCTION_MIMICCALL_BYREF, + external_call_result_type.fn_type(mimic_call_arguments_by_ref.as_slice(), false), + Some(inkwell::module::Linkage::External), + ); + Function::set_default_attributes(llvm, mimic_call_byref, optimizer); + + let r#return = Self::declare( + module, + Self::FUNCTION_RETURN, + llvm.void_type().fn_type( + vec![ + llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32) + .as_basic_type_enum() + .into(); + 3 + ] + .as_slice(), + false, + ), + Some(inkwell::module::Linkage::External), + ); + Function::set_default_attributes(llvm, r#return, optimizer); + let revert = Self::declare( + module, + Self::FUNCTION_REVERT, + llvm.void_type().fn_type( + vec![ + llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32) + .as_basic_type_enum() + .into(); + 3 + ] + .as_slice(), + false, + ), + Some(inkwell::module::Linkage::External), + ); + Function::set_default_attributes(llvm, revert, optimizer); + + Self { + personality, + cxa_throw, + + div, + sdiv, + r#mod, + smod, + + shl, + shr, + sar, + byte, + + add_mod, + mul_mod, + exp, + sign_extend, + + mstore8, + + sha3, + + system_request, + + //far_call, + static_call, + delegate_call, + mimic_call, + + far_call_byref, + static_call_byref, + delegate_call_byref, + mimic_call_byref, + + r#return, + revert, + } + } + + /// + /// Declares an LLVM runtime function in the `module`, + /// + pub fn declare( + module: &inkwell::module::Module<'ctx>, + name: &str, + r#type: inkwell::types::FunctionType<'ctx>, + linkage: Option, + ) -> FunctionDeclaration<'ctx> { + let value = module.add_function(name, r#type, linkage); + FunctionDeclaration::new(r#type, value) + } + + /// + /// Modifies the external call function with `is_byref` and `is_system` modifiers. + /// + pub fn modify( + &self, + function: FunctionDeclaration<'ctx>, + is_byref: bool, + ) -> anyhow::Result> { + let modified = if + /*function == self.far_call { + match is_byref { + false => self.far_call, + true => self.far_call_byref, + } + } else if */ + function == self.static_call { + match is_byref { + false => self.static_call, + true => self.static_call_byref, + } + } else if function == self.delegate_call { + match is_byref { + false => self.delegate_call, + true => self.delegate_call_byref, + } + } else if function == self.mimic_call { + match is_byref { + false => self.mimic_call, + true => self.mimic_call_byref, + } + } else { + anyhow::bail!( + "Cannot modify an external call function `{}`", + function.value.get_name().to_string_lossy() + ); + }; + + Ok(modified) + } +} diff --git a/crates/llvm-context/src/eravm/context/function/mod.rs b/crates/llvm-context/src/eravm/context/function/mod.rs new file mode 100644 index 0000000..82013fc --- /dev/null +++ b/crates/llvm-context/src/eravm/context/function/mod.rs @@ -0,0 +1,428 @@ +//! +//! The LLVM IR generator function. +//! + +pub mod block; +pub mod declaration; +pub mod evmla_data; +pub mod intrinsics; +pub mod llvm_runtime; +pub mod r#return; +pub mod runtime; +pub mod vyper_data; +pub mod yul_data; + +use std::collections::HashMap; + +use crate::eravm::context::attribute::Attribute; +use crate::eravm::context::pointer::Pointer; +use crate::optimizer::settings::size_level::SizeLevel; +use crate::optimizer::Optimizer; + +use self::declaration::Declaration; +use self::evmla_data::EVMLAData; +use self::r#return::Return; +use self::runtime::Runtime; +use self::vyper_data::VyperData; +use self::yul_data::YulData; + +/// +/// The LLVM IR generator function. +/// +#[derive(Debug)] +pub struct Function<'ctx> { + /// The high-level source code name. + name: String, + /// The LLVM function declaration. + declaration: Declaration<'ctx>, + /// The stack representation. + stack: HashMap>, + /// The return value entity. + r#return: Return<'ctx>, + + /// The entry block. Each LLVM IR functions must have an entry block. + entry_block: inkwell::basic_block::BasicBlock<'ctx>, + /// The return/leave block. LLVM IR functions may have multiple returning blocks, but it is + /// more reasonable to have a single returning block and other high-level language returns + /// jumping to it. This way it is easier to implement some additional checks and clean-ups + /// before the returning. + return_block: inkwell::basic_block::BasicBlock<'ctx>, + + /// The Yul compiler data. + yul_data: Option, + /// The EVM legacy assembly compiler data. + evmla_data: Option>, + /// The Vyper data. + vyper_data: Option, +} + +impl<'ctx> Function<'ctx> { + /// The near call ABI function prefix. + pub const ZKSYNC_NEAR_CALL_ABI_PREFIX: &'static str = "ZKSYNC_NEAR_CALL"; + + /// The near call ABI exception handler name. + pub const ZKSYNC_NEAR_CALL_ABI_EXCEPTION_HANDLER: &'static str = "ZKSYNC_CATCH_NEAR_CALL"; + + /// The stack hashmap default capacity. + const STACK_HASHMAP_INITIAL_CAPACITY: usize = 64; + + /// + /// A shortcut constructor. + /// + pub fn new( + name: String, + declaration: Declaration<'ctx>, + r#return: Return<'ctx>, + + entry_block: inkwell::basic_block::BasicBlock<'ctx>, + return_block: inkwell::basic_block::BasicBlock<'ctx>, + ) -> Self { + Self { + name, + declaration, + stack: HashMap::with_capacity(Self::STACK_HASHMAP_INITIAL_CAPACITY), + r#return, + + entry_block, + return_block, + + yul_data: None, + evmla_data: None, + vyper_data: None, + } + } + + /// + /// Returns the function name reference. + /// + pub fn name(&self) -> &str { + self.name.as_str() + } + + /// + /// Checks whether the function is defined outside of the front-end. + /// + pub fn is_name_external(name: &str) -> bool { + name.starts_with("llvm.") + || (name.starts_with("__") + && name != Runtime::FUNCTION_ENTRY + && name != Runtime::FUNCTION_DEPLOY_CODE + && name != Runtime::FUNCTION_RUNTIME_CODE) + } + + /// + /// Checks whether the function is related to the near call ABI. + /// + pub fn is_near_call_abi(name: &str) -> bool { + name.starts_with(Self::ZKSYNC_NEAR_CALL_ABI_PREFIX) + || name == Self::ZKSYNC_NEAR_CALL_ABI_EXCEPTION_HANDLER + } + + /// + /// Returns the LLVM function declaration. + /// + pub fn declaration(&self) -> Declaration<'ctx> { + self.declaration + } + + /// + /// Returns the N-th parameter of the function. + /// + pub fn get_nth_param(&self, index: usize) -> inkwell::values::BasicValueEnum<'ctx> { + self.declaration() + .value + .get_nth_param(index as u32) + .expect("Always exists") + } + + /// + /// Sets the memory writer function attributes. + /// + pub fn set_attributes( + llvm: &'ctx inkwell::context::Context, + declaration: Declaration<'ctx>, + attributes: Vec, + force: bool, + ) { + for attribute_kind in attributes.into_iter() { + match attribute_kind { + attribute_kind @ Attribute::AlwaysInline if force => { + let is_optimize_none_set = declaration + .value + .get_enum_attribute( + inkwell::attributes::AttributeLoc::Function, + Attribute::OptimizeNone as u32, + ) + .is_some(); + if !is_optimize_none_set { + declaration.value.remove_enum_attribute( + inkwell::attributes::AttributeLoc::Function, + Attribute::NoInline as u32, + ); + declaration.value.add_attribute( + inkwell::attributes::AttributeLoc::Function, + llvm.create_enum_attribute(attribute_kind as u32, 0), + ); + } + } + attribute_kind @ Attribute::NoInline if force => { + declaration.value.remove_enum_attribute( + inkwell::attributes::AttributeLoc::Function, + Attribute::AlwaysInline as u32, + ); + declaration.value.add_attribute( + inkwell::attributes::AttributeLoc::Function, + llvm.create_enum_attribute(attribute_kind as u32, 0), + ); + } + attribute_kind => declaration.value.add_attribute( + inkwell::attributes::AttributeLoc::Function, + llvm.create_enum_attribute(attribute_kind as u32, 0), + ), + } + } + } + + /// + /// Sets the default attributes. + /// + /// The attributes only affect the LLVM optimizations. + /// + pub fn set_default_attributes( + llvm: &'ctx inkwell::context::Context, + declaration: Declaration<'ctx>, + optimizer: &Optimizer, + ) { + if optimizer.settings().level_middle_end == inkwell::OptimizationLevel::None { + Self::set_attributes( + llvm, + declaration, + vec![Attribute::OptimizeNone, Attribute::NoInline], + false, + ); + } else if optimizer.settings().level_middle_end_size == SizeLevel::Z { + Self::set_attributes( + llvm, + declaration, + vec![Attribute::OptimizeForSize, Attribute::MinSize], + false, + ); + } + + Self::set_attributes(llvm, declaration, vec![Attribute::NoFree], false); + } + + /// + /// Sets the front-end runtime attributes. + /// + pub fn set_frontend_runtime_attributes( + llvm: &'ctx inkwell::context::Context, + declaration: Declaration<'ctx>, + optimizer: &Optimizer, + ) { + if optimizer.settings().level_middle_end_size == SizeLevel::Z { + Self::set_attributes(llvm, declaration, vec![Attribute::NoInline], false); + } + } + + /// + /// Sets the exception handler attributes. + /// + pub fn set_exception_handler_attributes( + llvm: &'ctx inkwell::context::Context, + declaration: Declaration<'ctx>, + ) { + Self::set_attributes(llvm, declaration, vec![Attribute::NoInline], false); + } + + /// + /// Sets the CXA-throw attributes. + /// + pub fn set_cxa_throw_attributes( + llvm: &'ctx inkwell::context::Context, + declaration: Declaration<'ctx>, + ) { + Self::set_attributes(llvm, declaration, vec![Attribute::NoProfile], false); + } + + /// + /// Sets the pure function attributes. + /// + pub fn set_pure_function_attributes( + llvm: &'ctx inkwell::context::Context, + declaration: Declaration<'ctx>, + ) { + Self::set_attributes( + llvm, + declaration, + vec![ + Attribute::MustProgress, + Attribute::NoUnwind, + // FIXME: LLVM complains about ReadNone being not valid for fns + // Attribute::ReadNone, + Attribute::WillReturn, + ], + false, + ); + } + + /// + /// Saves the pointer to a stack variable, returning the pointer to the shadowed variable, + /// if it exists. + /// + pub fn insert_stack_pointer( + &mut self, + name: String, + pointer: Pointer<'ctx>, + ) -> Option> { + self.stack.insert(name, pointer) + } + + /// + /// Gets the pointer to a stack variable. + /// + pub fn get_stack_pointer(&self, name: &str) -> Option> { + self.stack.get(name).copied() + } + + /// + /// Removes the pointer to a stack variable. + /// + pub fn remove_stack_pointer(&mut self, name: &str) { + self.stack.remove(name); + } + + /// + /// Returns the return entity representation. + /// + pub fn r#return(&self) -> Return<'ctx> { + self.r#return + } + + /// + /// Returns the pointer to the function return value. + /// + /// # Panics + /// If the pointer has not been set yet. + /// + pub fn return_pointer(&self) -> Option> { + self.r#return.return_pointer() + } + + /// + /// Returns the return data size in bytes, based on the default stack alignment. + /// + /// # Panics + /// If the pointer has not been set yet. + /// + pub fn return_data_size(&self) -> usize { + self.r#return.return_data_size() + } + + /// + /// Returns the function entry block. + /// + pub fn entry_block(&self) -> inkwell::basic_block::BasicBlock<'ctx> { + self.entry_block + } + + /// + /// Returns the function return block. + /// + pub fn return_block(&self) -> inkwell::basic_block::BasicBlock<'ctx> { + self.return_block + } + + /// + /// Sets the EVM legacy assembly data. + /// + pub fn set_evmla_data(&mut self, data: EVMLAData<'ctx>) { + self.evmla_data = Some(data); + } + + /// + /// Returns the EVM legacy assembly data reference. + /// + /// # Panics + /// If the EVM data has not been initialized. + /// + pub fn evmla(&self) -> &EVMLAData<'ctx> { + self.evmla_data + .as_ref() + .expect("The EVM data must have been initialized") + } + + /// + /// Returns the EVM legacy assembly data mutable reference. + /// + /// # Panics + /// If the EVM data has not been initialized. + /// + pub fn evmla_mut(&mut self) -> &mut EVMLAData<'ctx> { + self.evmla_data + .as_mut() + .expect("The EVM data must have been initialized") + } + + /// + /// Sets the Vyper data. + /// + pub fn set_vyper_data(&mut self, data: VyperData) { + self.vyper_data = Some(data); + } + + /// + /// Returns the Vyper data reference. + /// + /// # Panics + /// If the Vyper data has not been initialized. + /// + pub fn vyper(&self) -> &VyperData { + self.vyper_data + .as_ref() + .expect("The Vyper data must have been initialized") + } + + /// + /// Returns the Vyper data mutable reference. + /// + /// # Panics + /// If the Vyper data has not been initialized. + /// + pub fn vyper_mut(&mut self) -> &mut VyperData { + self.vyper_data + .as_mut() + .expect("The Vyper data must have been initialized") + } + + /// + /// Sets the Yul data. + /// + pub fn set_yul_data(&mut self, data: YulData) { + self.yul_data = Some(data); + } + + /// + /// Returns the Yul data reference. + /// + /// # Panics + /// If the Yul data has not been initialized. + /// + pub fn yul(&self) -> &YulData { + self.yul_data + .as_ref() + .expect("The Yul data must have been initialized") + } + + /// + /// Returns the Yul data mutable reference. + /// + /// # Panics + /// If the Yul data has not been initialized. + /// + pub fn yul_mut(&mut self) -> &mut YulData { + self.yul_data + .as_mut() + .expect("The Yul data must have been initialized") + } +} diff --git a/crates/llvm-context/src/eravm/context/function/return.rs b/crates/llvm-context/src/eravm/context/function/return.rs new file mode 100644 index 0000000..01abe28 --- /dev/null +++ b/crates/llvm-context/src/eravm/context/function/return.rs @@ -0,0 +1,73 @@ +//! +//! The LLVM IR generator function return entity. +//! + +use crate::eravm::context::pointer::Pointer; + +/// +/// The LLVM IR generator function return entity. +/// +#[derive(Debug, Clone, Copy)] +pub enum Return<'ctx> { + /// The function does not return a value. + None, + /// The function returns a primitive value. + Primitive { + /// The primitive value pointer allocated at the function entry. + pointer: Pointer<'ctx>, + }, + /// The function returns a compound value. + /// In this case, the return pointer is allocated on the stack by the callee. + Compound { + /// The structure pointer allocated at the function entry. + pointer: Pointer<'ctx>, + /// The function return type size. + size: usize, + }, +} + +impl<'ctx> Return<'ctx> { + /// + /// A shortcut constructor. + /// + pub fn none() -> Self { + Self::None + } + + /// + /// A shortcut constructor. + /// + pub fn primitive(pointer: Pointer<'ctx>) -> Self { + Self::Primitive { pointer } + } + + /// + /// A shortcut constructor. + /// + pub fn compound(pointer: Pointer<'ctx>, size: usize) -> Self { + Self::Compound { pointer, size } + } + + /// + /// Returns the pointer to the function return value. + /// + pub fn return_pointer(&self) -> Option> { + match self { + Return::None => None, + Return::Primitive { pointer } => Some(pointer.to_owned()), + Return::Compound { pointer, .. } => Some(pointer.to_owned()), + } + } + + /// + /// Returns the return data size in bytes, based on the default stack alignment. + /// + pub fn return_data_size(&self) -> usize { + era_compiler_common::BYTE_LENGTH_FIELD + * match self { + Self::None => 0, + Self::Primitive { .. } => 1, + Self::Compound { size, .. } => *size, + } + } +} diff --git a/crates/llvm-context/src/eravm/context/function/runtime/default_call.rs b/crates/llvm-context/src/eravm/context/function/runtime/default_call.rs new file mode 100644 index 0000000..ac91ec6 --- /dev/null +++ b/crates/llvm-context/src/eravm/context/function/runtime/default_call.rs @@ -0,0 +1,257 @@ +//! +//! The `default_call` function. +//! + +use inkwell::types::BasicType; +use inkwell::values::BasicValue; + +use crate::eravm::context::address_space::AddressSpace; +use crate::eravm::context::function::declaration::Declaration as FunctionDeclaration; +use crate::eravm::context::function::llvm_runtime::LLVMRuntime; +use crate::eravm::context::function::Function; +use crate::eravm::context::pointer::Pointer; +use crate::eravm::context::Context; +use crate::eravm::Dependency; +use crate::eravm::WriteLLVM; + +/// +/// The `default_call` function. +/// +/// Generates a default contract call, if the `msg.value` is zero. +/// +#[derive(Debug)] +pub struct DefaultCall { + /// The name of the inner function used for the low-level call. + inner_name: String, + /// The function name with the low-level function name as an element. + name: String, +} + +impl DefaultCall { + /// The gas argument index. + pub const ARGUMENT_INDEX_GAS: usize = 0; + + /// The address argument index. + pub const ARGUMENT_INDEX_ADDRESS: usize = 1; + + /// The input offset argument index. + pub const ARGUMENT_INDEX_INPUT_OFFSET: usize = 2; + + /// The input length argument index. + pub const ARGUMENT_INDEX_INPUT_LENGTH: usize = 3; + + /// The output offset argument index. + pub const ARGUMENT_INDEX_OUTPUT_OFFSET: usize = 4; + + /// The output length argument index. + pub const ARGUMENT_INDEX_OUTPUT_LENGTH: usize = 5; + + /// + /// A shortcut constructor. + /// + pub fn new(call_function: FunctionDeclaration) -> Self { + let inner_name = call_function.value.get_name().to_string_lossy().to_string(); + let name = Self::name(call_function); + + Self { inner_name, name } + } + + /// + /// Returns the function name. + /// + pub fn name(call_function: FunctionDeclaration) -> String { + let suffix = match call_function.value.get_name().to_string_lossy() { + name if name == LLVMRuntime::FUNCTION_FARCALL => "far", + name if name == LLVMRuntime::FUNCTION_STATICCALL => "static", + name if name == LLVMRuntime::FUNCTION_DELEGATECALL => "delegate", + name => panic!("Invalid low-level call inner function `{name}`"), + }; + format!("__default_{suffix}_call") + } + + /// + /// Returns the low-level call function. + /// + fn inner_function<'ctx, D>(&self, context: &Context<'ctx, D>) -> FunctionDeclaration<'ctx> + where + D: Dependency + Clone, + { + match self.inner_name.as_str() { + //name if name == LLVMRuntime::FUNCTION_FARCALL => context.llvm_runtime().far_call, + name if name == LLVMRuntime::FUNCTION_STATICCALL => context.llvm_runtime().static_call, + name if name == LLVMRuntime::FUNCTION_DELEGATECALL => { + context.llvm_runtime().delegate_call + } + name => panic!("Invalid low-level call inner function `{name}`"), + } + } +} + +impl WriteLLVM for DefaultCall +where + D: Dependency + Clone, +{ + fn declare(&mut self, context: &mut Context) -> anyhow::Result<()> { + let function_type = context.function_type( + vec![ + context.field_type().as_basic_type_enum(), + context.field_type().as_basic_type_enum(), + context.field_type().as_basic_type_enum(), + context.field_type().as_basic_type_enum(), + context.field_type().as_basic_type_enum(), + context.field_type().as_basic_type_enum(), + ], + 1, + false, + ); + let function = context.add_function( + self.name.as_str(), + function_type, + 1, + Some(inkwell::module::Linkage::Private), + )?; + Function::set_frontend_runtime_attributes( + context.llvm, + function.borrow().declaration(), + &context.optimizer, + ); + + Ok(()) + } + + fn into_llvm(self, context: &mut Context) -> anyhow::Result<()> { + context.set_current_function(self.name.as_str())?; + + /* + let gas = context + .current_function() + .borrow() + .get_nth_param(Self::ARGUMENT_INDEX_GAS) + .into_int_value(); + let address = context + .current_function() + .borrow() + .get_nth_param(Self::ARGUMENT_INDEX_ADDRESS) + .into_int_value(); + let input_offset = context + .current_function() + .borrow() + .get_nth_param(Self::ARGUMENT_INDEX_INPUT_OFFSET) + .into_int_value(); + let input_length = context + .current_function() + .borrow() + .get_nth_param(Self::ARGUMENT_INDEX_INPUT_LENGTH) + .into_int_value(); + let output_offset = context + .current_function() + .borrow() + .get_nth_param(Self::ARGUMENT_INDEX_OUTPUT_OFFSET) + .into_int_value(); + let output_length = context + .current_function() + .borrow() + .get_nth_param(Self::ARGUMENT_INDEX_OUTPUT_LENGTH) + .into_int_value(); + */ + context.set_basic_block(context.current_function().borrow().entry_block()); + + let status_code_result_pointer = context.build_alloca( + context.field_type(), + "contract_call_result_status_code_pointer", + ); + /* + context.build_store(status_code_result_pointer, context.field_const(0)); + + let abi_data = crate::eravm::utils::abi_data( + context, + input_offset, + input_length, + Some(gas), + AddressSpace::Heap, + false, + )? + .into_int_value(); + + let result = context + .build_call( + self.inner_function(context), + crate::eravm::utils::external_call_arguments( + context, + abi_data.as_basic_value_enum(), + address, + vec![], + None, + ) + .as_slice(), + "contract_call_external", + ) + .expect("IntrinsicFunction always returns a flag"); + + let result_abi_data = context + .builder() + .build_extract_value( + result.into_struct_value(), + 0, + "contract_call_external_result_abi_data", + ) + .expect("Always exists"); + let result_abi_data_pointer = Pointer::new( + context.byte_type(), + AddressSpace::Generic, + result_abi_data.into_pointer_value(), + ); + let result_abi_data_casted = result_abi_data_pointer.cast(context.field_type()); + + let result_status_code_boolean = context + .builder() + .build_extract_value( + result.into_struct_value(), + 1, + "contract_call_external_result_status_code_boolean", + ) + .expect("Always exists"); + let result_status_code = context.builder().build_int_z_extend_or_bit_cast( + result_status_code_boolean.into_int_value(), + context.field_type(), + "contract_call_external_result_status_code", + )?; + context.build_store(status_code_result_pointer, result_status_code); + + let source = result_abi_data_casted; + + let destination = Pointer::new_with_offset( + context, + AddressSpace::Heap, + context.byte_type(), + output_offset, + "contract_call_destination", + ); + + context.build_memcpy_return_data( + context.intrinsics().memory_copy_from_generic, + destination, + source, + output_length, + "contract_call_memcpy_from_child", + ); + + context.write_abi_pointer( + result_abi_data_pointer, + crate::eravm::GLOBAL_RETURN_DATA_POINTER, + ); + context.write_abi_data_size( + result_abi_data_pointer, + crate::eravm::GLOBAL_RETURN_DATA_SIZE, + ); + */ + context.build_unconditional_branch(context.current_function().borrow().return_block()); + + context.set_basic_block(context.current_function().borrow().return_block()); + let status_code_result = + context.build_load(status_code_result_pointer, "contract_call_status_code")?; + context.build_return(Some(&status_code_result)); + + Ok(()) + } +} diff --git a/crates/llvm-context/src/eravm/context/function/runtime/deploy_code.rs b/crates/llvm-context/src/eravm/context/function/runtime/deploy_code.rs new file mode 100644 index 0000000..351d742 --- /dev/null +++ b/crates/llvm-context/src/eravm/context/function/runtime/deploy_code.rs @@ -0,0 +1,105 @@ +//! +//! The deploy code function. +//! + +use std::marker::PhantomData; + +use crate::eravm::context::address_space::AddressSpace; +use crate::eravm::context::code_type::CodeType; +use crate::eravm::context::function::runtime::Runtime; +use crate::eravm::context::pointer::Pointer; +use crate::eravm::context::Context; +use crate::eravm::Dependency; +use crate::eravm::WriteLLVM; + +/// +/// The deploy code function. +/// +/// Is a special function that is only used by the front-end generated code. +/// +#[derive(Debug)] +pub struct DeployCode +where + B: WriteLLVM, + D: Dependency + Clone, +{ + /// The deploy code AST representation. + inner: B, + /// The `D` phantom data. + _pd: PhantomData, +} + +impl DeployCode +where + B: WriteLLVM, + D: Dependency + Clone, +{ + /// + /// A shortcut constructor. + /// + pub fn new(inner: B) -> Self { + Self { + inner, + _pd: PhantomData, + } + } +} + +impl WriteLLVM for DeployCode +where + B: WriteLLVM, + D: Dependency + Clone, +{ + fn declare(&mut self, context: &mut Context) -> anyhow::Result<()> { + let function_type = + context.function_type::(vec![], 0, false); + context.add_function( + Runtime::FUNCTION_DEPLOY_CODE, + function_type, + 0, + Some(inkwell::module::Linkage::External), + )?; + + self.inner.declare(context) + } + + fn into_llvm(self, context: &mut Context) -> anyhow::Result<()> { + context.set_current_function(Runtime::FUNCTION_DEPLOY_CODE)?; + + context.set_basic_block(context.current_function().borrow().entry_block()); + context.set_code_type(CodeType::Deploy); + if let Some(vyper) = context.vyper_data.as_ref() { + for index in 0..vyper.immutables_size() / era_compiler_common::BYTE_LENGTH_FIELD { + let offset = (crate::eravm::r#const::HEAP_AUX_OFFSET_CONSTRUCTOR_RETURN_DATA + as usize) + + (1 + index) * 2 * era_compiler_common::BYTE_LENGTH_FIELD; + let value = index * era_compiler_common::BYTE_LENGTH_FIELD; + let pointer = Pointer::new_with_offset( + context, + AddressSpace::HeapAuxiliary, + context.field_type(), + context.field_const(offset as u64), + "immutable_index_initializer", + ); + context.build_store(pointer, context.field_const(value as u64))?; + } + } + + self.inner.into_llvm(context)?; + match context + .basic_block() + .get_last_instruction() + .map(|instruction| instruction.get_opcode()) + { + Some(inkwell::values::InstructionOpcode::Br) => {} + Some(inkwell::values::InstructionOpcode::Switch) => {} + _ => context + .build_unconditional_branch(context.current_function().borrow().return_block()), + } + + context.set_basic_block(context.current_function().borrow().return_block()); + context.build_return(None); + + Ok(()) + } +} diff --git a/crates/llvm-context/src/eravm/context/function/runtime/deployer_call.rs b/crates/llvm-context/src/eravm/context/function/runtime/deployer_call.rs new file mode 100644 index 0000000..219d302 --- /dev/null +++ b/crates/llvm-context/src/eravm/context/function/runtime/deployer_call.rs @@ -0,0 +1,333 @@ +//! +//! The `deployer_call` function. +//! + +use inkwell::types::BasicType; +use inkwell::values::BasicValue; + +use crate::eravm::context::address_space::AddressSpace; +use crate::eravm::context::function::Function; +use crate::eravm::context::pointer::Pointer; +use crate::eravm::context::Context; +use crate::eravm::Dependency; +use crate::eravm::WriteLLVM; + +/// +/// The `deployer_call` function. +/// +/// Calls the deployer system contract, which returns the newly deployed contract address or 0. +/// +/// The address is returned in the first 32-byte word of the return data. If it is 0, the 0 is +/// returned. If the entire call has failed, there is also a 0 returned. +/// +#[derive(Debug)] +pub struct DeployerCall { + /// The address space where the calldata is allocated. + /// Solidity uses the ordinary heap. Vyper uses the auxiliary heap. + address_space: AddressSpace, +} + +impl DeployerCall { + /// The default function name. + pub const FUNCTION_NAME: &'static str = "__deployer_call"; + + /// The value argument index. + pub const ARGUMENT_INDEX_VALUE: usize = 0; + + /// The input offset argument index. + pub const ARGUMENT_INDEX_INPUT_OFFSET: usize = 1; + + /// The input length argument index. + pub const ARGUMENT_INDEX_INPUT_LENGTH: usize = 2; + + /// The signature hash argument index. + pub const ARGUMENT_INDEX_SIGNATURE_HASH: usize = 3; + + /// The salt argument index. + pub const ARGUMENT_INDEX_SALT: usize = 4; + + /// + /// A shortcut constructor. + /// + pub fn new(address_space: AddressSpace) -> Self { + Self { address_space } + } +} + +impl WriteLLVM for DeployerCall +where + D: Dependency + Clone, +{ + fn declare(&mut self, context: &mut Context) -> anyhow::Result<()> { + let function_type = context.function_type( + vec![ + context.field_type().as_basic_type_enum(), + context.field_type().as_basic_type_enum(), + context.field_type().as_basic_type_enum(), + context.field_type().as_basic_type_enum(), + context.field_type().as_basic_type_enum(), + ], + 1, + false, + ); + let function = context.add_function( + Self::FUNCTION_NAME, + function_type, + 1, + Some(inkwell::module::Linkage::External), + )?; + Function::set_frontend_runtime_attributes( + context.llvm, + function.borrow().declaration(), + &context.optimizer, + ); + + Ok(()) + } + + fn into_llvm(self, context: &mut Context) -> anyhow::Result<()> { + context.set_current_function(Self::FUNCTION_NAME)?; + + let value = context + .current_function() + .borrow() + .get_nth_param(Self::ARGUMENT_INDEX_VALUE) + .into_int_value(); + let input_offset = context + .current_function() + .borrow() + .get_nth_param(Self::ARGUMENT_INDEX_INPUT_OFFSET) + .into_int_value(); + let input_length = context + .current_function() + .borrow() + .get_nth_param(Self::ARGUMENT_INDEX_INPUT_LENGTH) + .into_int_value(); + let signature_hash = context + .current_function() + .borrow() + .get_nth_param(Self::ARGUMENT_INDEX_SIGNATURE_HASH) + .into_int_value(); + let salt = context + .current_function() + .borrow() + .get_nth_param(Self::ARGUMENT_INDEX_SALT) + .into_int_value(); + + let error_block = context.append_basic_block("deployer_call_error_block"); + let success_block = context.append_basic_block("deployer_call_success_block"); + let value_zero_block = context.append_basic_block("deployer_call_value_zero_block"); + let value_non_zero_block = context.append_basic_block("deployer_call_value_non_zero_block"); + let value_join_block = context.append_basic_block("deployer_call_value_join_block"); + + context.set_basic_block(context.current_function().borrow().entry_block()); + let abi_data = crate::eravm::utils::abi_data( + context, + input_offset, + input_length, + None, + self.address_space, + true, + )?; + + let signature_pointer = Pointer::new_with_offset( + context, + self.address_space, + context.field_type(), + input_offset, + "deployer_call_signature_pointer", + ); + context.build_store(signature_pointer, signature_hash)?; + + let salt_offset = context.builder().build_int_add( + input_offset, + context.field_const(era_compiler_common::BYTE_LENGTH_X32 as u64), + "deployer_call_salt_offset", + )?; + let salt_pointer = Pointer::new_with_offset( + context, + self.address_space, + context.field_type(), + salt_offset, + "deployer_call_salt_pointer", + ); + context.build_store(salt_pointer, salt)?; + + let arguments_offset_offset = context.builder().build_int_add( + salt_offset, + context.field_const((era_compiler_common::BYTE_LENGTH_FIELD * 2) as u64), + "deployer_call_arguments_offset_offset", + )?; + let arguments_offset_pointer = Pointer::new_with_offset( + context, + self.address_space, + context.field_type(), + arguments_offset_offset, + "deployer_call_arguments_offset_pointer", + ); + context.build_store( + arguments_offset_pointer, + context.field_const( + (crate::eravm::DEPLOYER_CALL_HEADER_SIZE + - (era_compiler_common::BYTE_LENGTH_X32 + + era_compiler_common::BYTE_LENGTH_FIELD)) as u64, + ), + )?; + + let arguments_length_offset = context.builder().build_int_add( + arguments_offset_offset, + context.field_const(era_compiler_common::BYTE_LENGTH_FIELD as u64), + "deployer_call_arguments_length_offset", + )?; + let arguments_length_pointer = Pointer::new_with_offset( + context, + self.address_space, + context.field_type(), + arguments_length_offset, + "deployer_call_arguments_length_pointer", + ); + let arguments_length_value = context.builder().build_int_sub( + input_length, + context.field_const(crate::eravm::DEPLOYER_CALL_HEADER_SIZE as u64), + "deployer_call_arguments_length", + )?; + context.build_store(arguments_length_pointer, arguments_length_value)?; + + let result_pointer = + context.build_alloca(context.field_type(), "deployer_call_result_pointer"); + context.build_store(result_pointer, context.field_const(0))?; + let deployer_call_result_type = context.structure_type(&[ + context + .byte_type() + .ptr_type(AddressSpace::Generic.into()) + .as_basic_type_enum(), + context.bool_type().as_basic_type_enum(), + ]); + let deployer_call_result_pointer = + context.build_alloca(deployer_call_result_type, "deployer_call_result_pointer"); + context.build_store( + deployer_call_result_pointer, + deployer_call_result_type.const_zero(), + )?; + let is_value_zero = context.builder().build_int_compare( + inkwell::IntPredicate::EQ, + value, + context.field_const(0), + "deployer_call_is_value_zero", + )?; + context.build_conditional_branch(is_value_zero, value_zero_block, value_non_zero_block)?; + + context.set_basic_block(value_zero_block); + //let deployer_call_result = context + // .build_call( + // context.llvm_runtime().far_call, + // crate::eravm::utils::external_call_arguments( + // context, + // abi_data, + // context.field_const(zkevm_opcode_defs::ADDRESS_CONTRACT_DEPLOYER.into()), + // vec![], + // None, + // ) + // .as_slice(), + // "deployer_call_ordinary", + // ) + // .expect("Always returns a value"); + //context.build_store(deployer_call_result_pointer, deployer_call_result)?; + context.build_unconditional_branch(value_join_block); + + context.set_basic_block(value_non_zero_block); + //let deployer_call_result = context + // .build_call( + // context.llvm_runtime().far_call, + // crate::eravm::utils::external_call_arguments( + // context, + // abi_data.as_basic_value_enum(), + // context.field_const(zkevm_opcode_defs::ADDRESS_MSG_VALUE.into()), + // vec![ + // value, + // context.field_const(zkevm_opcode_defs::ADDRESS_CONTRACT_DEPLOYER.into()), + // context.field_const(u64::from(crate::eravm::r#const::SYSTEM_CALL_BIT)), + // ], + // None, + // ) + // .as_slice(), + // "deployer_call_system", + // ) + // .expect("Always returns a value"); + //context.build_store(deployer_call_result_pointer, deployer_call_result)?; + context.build_unconditional_branch(value_join_block); + + context.set_basic_block(value_join_block); + let result_abi_data_pointer = context.build_gep( + deployer_call_result_pointer, + &[ + context.field_const(0), + context + .integer_type(era_compiler_common::BIT_LENGTH_X32) + .const_zero(), + ], + context + .byte_type() + .ptr_type(AddressSpace::Generic.into()) + .as_basic_type_enum(), + "deployer_call_result_abi_data_pointer", + ); + let result_abi_data = + context.build_load(result_abi_data_pointer, "deployer_call_result_abi_data")?; + + let result_status_code_pointer = context.build_gep( + deployer_call_result_pointer, + &[ + context.field_const(0), + context + .integer_type(era_compiler_common::BIT_LENGTH_X32) + .const_int(1, false), + ], + context.bool_type().as_basic_type_enum(), + "contract_call_external_result_status_code_pointer", + ); + let result_status_code_boolean = context + .build_load( + result_status_code_pointer, + "contract_call_external_result_status_code_boolean", + )? + .into_int_value(); + + context.build_conditional_branch(result_status_code_boolean, success_block, error_block)?; + + context.set_basic_block(success_block); + let result_abi_data_pointer = Pointer::new( + context.field_type(), + AddressSpace::Generic, + result_abi_data.into_pointer_value(), + ); + let address_or_status_code = context.build_load( + result_abi_data_pointer, + "deployer_call_address_or_status_code", + )?; + context.build_store(result_pointer, address_or_status_code)?; + context.build_unconditional_branch(context.current_function().borrow().return_block()); + + context.set_basic_block(error_block); + let result_abi_data_pointer = Pointer::new( + context.byte_type(), + AddressSpace::Generic, + result_abi_data.into_pointer_value(), + ); + context.write_abi_pointer( + result_abi_data_pointer, + crate::eravm::GLOBAL_RETURN_DATA_POINTER, + ); + context.write_abi_data_size( + result_abi_data_pointer, + crate::eravm::GLOBAL_RETURN_DATA_SIZE, + ); + context.build_unconditional_branch(context.current_function().borrow().return_block()); + + context.set_basic_block(context.current_function().borrow().return_block()); + let result = context.build_load(result_pointer, "deployer_call_result")?; + context.build_return(Some(&result)); + + Ok(()) + } +} diff --git a/crates/llvm-context/src/eravm/context/function/runtime/entry.rs b/crates/llvm-context/src/eravm/context/function/runtime/entry.rs new file mode 100644 index 0000000..2c2db30 --- /dev/null +++ b/crates/llvm-context/src/eravm/context/function/runtime/entry.rs @@ -0,0 +1,269 @@ +//! +//! The entry function. +//! + +use inkwell::types::BasicType; + +use crate::eravm::context::address_space::AddressSpace; +use crate::eravm::context::function::runtime::Runtime; +use crate::eravm::context::Context; +use crate::eravm::Dependency; +use crate::eravm::WriteLLVM; +use crate::EraVMPointer as Pointer; + +/// +/// The entry function. +/// +/// The function is a wrapper managing the runtime and deploy code calling logic. +/// +/// Is a special runtime function that is only used by the front-end generated code. +/// +#[derive(Debug, Default)] +pub struct Entry {} + +impl Entry { + /// The call flags argument index. + pub const ARGUMENT_INDEX_CALL_FLAGS: usize = 0; + + /// The number of mandatory arguments. + pub const MANDATORY_ARGUMENTS_COUNT: usize = 2; + + /// Initializes the global variables. + /// The pointers are not initialized, because it's not possible to create a null pointer. + pub fn initialize_globals(context: &mut Context) + where + D: Dependency + Clone, + { + let calldata_type = context.array_type(context.byte_type(), 1024); + context.set_global( + crate::eravm::GLOBAL_CALLDATA_POINTER, + calldata_type, + AddressSpace::Stack, + calldata_type.get_undef(), + ); + + let heap_memory_type = context.array_type(context.byte_type(), 1024 * 1024); + context.set_global( + crate::eravm::GLOBAL_HEAP_MEMORY_POINTER, + heap_memory_type, + AddressSpace::Stack, + heap_memory_type.get_undef(), + ); + + context.set_global( + crate::eravm::GLOBAL_CALLDATA_SIZE, + context.field_type(), + AddressSpace::Stack, + context.field_undef(), + ); + context.set_global( + crate::eravm::GLOBAL_RETURN_DATA_SIZE, + context.field_type(), + AddressSpace::Stack, + context.field_const(0), + ); + + context.set_global( + crate::eravm::GLOBAL_CALL_FLAGS, + context.field_type(), + AddressSpace::Stack, + context.field_const(0), + ); + + let extra_abi_data_type = context.array_type( + context.field_type().as_basic_type_enum(), + crate::eravm::EXTRA_ABI_DATA_SIZE, + ); + context.set_global( + crate::eravm::GLOBAL_EXTRA_ABI_DATA, + extra_abi_data_type, + AddressSpace::Stack, + extra_abi_data_type.const_zero(), + ); + } + + /// Load the calldata via seal `input` and initialize the calldata end + /// and calldata size globals. + pub fn load_calldata(context: &mut Context) -> anyhow::Result<()> + where + D: Dependency + Clone, + { + let input_pointer = context + .get_global(crate::eravm::GLOBAL_CALLDATA_POINTER)? + .value + .as_pointer_value(); + let input_pointer_casted = context.builder.build_ptr_to_int( + input_pointer, + context.integer_type(32), + "input_pointer_casted", + )?; + + let length_pointer = context.build_alloca(context.integer_type(32), "len_ptr"); + let length_pointer_casted = context.builder.build_ptr_to_int( + length_pointer.value, + context.integer_type(32), + "length_pointer_casted", + )?; + + context.build_store(length_pointer, context.integer_const(32, 1024))?; + context.builder().build_call( + context.module().get_function("input").expect("is declared"), + &[input_pointer_casted.into(), length_pointer_casted.into()], + "call_seal_input", + )?; + + // Store the calldata size + let calldata_size = context + .build_load(length_pointer, "input_size")? + .into_int_value(); + let calldata_size_casted = context.builder().build_int_z_extend( + calldata_size, + context.field_type(), + "zext_input_len", + )?; + context.set_global( + crate::eravm::GLOBAL_CALLDATA_SIZE, + context.field_type(), + AddressSpace::Stack, + calldata_size_casted, + ); + + // Store calldata end pointer + let input_pointer = Pointer::new( + input_pointer.get_type(), + AddressSpace::Generic, + input_pointer, + ); + let calldata_end_pointer = context.build_gep( + input_pointer, + &[calldata_size_casted], + context + .byte_type() + .ptr_type(AddressSpace::Generic.into()) + .as_basic_type_enum(), + "return_data_abi_initializer", + ); + context.write_abi_pointer( + calldata_end_pointer, + crate::eravm::GLOBAL_RETURN_DATA_POINTER, + ); + context.write_abi_pointer(calldata_end_pointer, crate::eravm::GLOBAL_ACTIVE_POINTER); + + Ok(()) + } + + /// Calls the deploy code if the first function argument was `1`. + /// Calls the runtime code otherwise. + pub fn leave_entry(context: &mut Context) -> anyhow::Result<()> + where + D: Dependency + Clone, + { + let is_deploy = context + .current_function() + .borrow() + .get_nth_param(Self::ARGUMENT_INDEX_CALL_FLAGS); + + context.set_global( + crate::eravm::GLOBAL_CALL_FLAGS, + is_deploy.get_type(), + AddressSpace::Stack, + is_deploy.into_int_value(), + ); + + let deploy_code_call_block = context.append_basic_block("deploy_code_call_block"); + let runtime_code_call_block = context.append_basic_block("runtime_code_call_block"); + + context.build_conditional_branch( + is_deploy.into_int_value(), + deploy_code_call_block, + runtime_code_call_block, + )?; + + let deploy_code = context + .functions + .get(Runtime::FUNCTION_DEPLOY_CODE) + .cloned() + .ok_or_else(|| anyhow::anyhow!("Contract deploy code not found"))?; + let runtime_code = context + .functions + .get(Runtime::FUNCTION_RUNTIME_CODE) + .cloned() + .ok_or_else(|| anyhow::anyhow!("Contract runtime code not found"))?; + + context.set_basic_block(deploy_code_call_block); + context.build_invoke(deploy_code.borrow().declaration, &[], "deploy_code_call"); + context.build_unconditional_branch(context.current_function().borrow().return_block()); + + context.set_basic_block(runtime_code_call_block); + context.build_invoke(runtime_code.borrow().declaration, &[], "runtime_code_call"); + context.build_unconditional_branch(context.current_function().borrow().return_block()); + + Ok(()) + } +} + +impl WriteLLVM for Entry +where + D: Dependency + Clone, +{ + fn declare(&mut self, context: &mut Context) -> anyhow::Result<()> { + let entry_arguments = vec![context.bool_type().as_basic_type_enum()]; + let entry_function_type = context.function_type(entry_arguments, 0, false); + context.add_function(Runtime::FUNCTION_ENTRY, entry_function_type, 0, None)?; + + context.declare_extern_function("deploy")?; + context.declare_extern_function("call")?; + + Ok(()) + } + + /// Instead of a single entrypoint, the runtime expects two exports: `call ` and `deploy`. + /// `call` and `deploy` directly call `entry`, signaling a deploy if the first arg is `1`. + /// The `entry` function loads calldata, sets globals and calls the runtime or deploy code. + fn into_llvm(self, context: &mut Context) -> anyhow::Result<()> { + let entry = context + .get_function(Runtime::FUNCTION_ENTRY) + .expect("the entry function should already be declared") + .borrow() + .declaration; + crate::EraVMFunction::set_attributes( + context.llvm(), + entry, + vec![crate::EraVMAttribute::NoReturn], + true, + ); + + context.set_current_function("deploy")?; + context.set_basic_block(context.current_function().borrow().entry_block()); + + assert!(context + .build_call(entry, &[context.bool_const(true).into()], "entry_deploy") + .is_none()); + + context.set_basic_block(context.current_function().borrow().return_block); + context.build_unreachable(); + + context.set_current_function("call")?; + context.set_basic_block(context.current_function().borrow().entry_block()); + + assert!(context + .build_call(entry, &[context.bool_const(false).into()], "entry_call") + .is_none()); + + context.set_basic_block(context.current_function().borrow().return_block); + context.build_unreachable(); + + context.set_current_function(Runtime::FUNCTION_ENTRY)?; + context.set_basic_block(context.current_function().borrow().entry_block()); + + Self::initialize_globals(context); + Self::load_calldata(context)?; + Self::leave_entry(context)?; + + context.build_unconditional_branch(context.current_function().borrow().return_block()); + context.set_basic_block(context.current_function().borrow().return_block()); + context.build_unreachable(); + + Ok(()) + } +} diff --git a/crates/llvm-context/src/eravm/context/function/runtime/mod.rs b/crates/llvm-context/src/eravm/context/function/runtime/mod.rs new file mode 100644 index 0000000..4c96fc5 --- /dev/null +++ b/crates/llvm-context/src/eravm/context/function/runtime/mod.rs @@ -0,0 +1,100 @@ +//! +//! The front-end runtime functions. +//! + +pub mod default_call; +pub mod deploy_code; +pub mod deployer_call; +pub mod entry; +pub mod runtime_code; + +use crate::eravm::context::address_space::AddressSpace; +use crate::eravm::context::function::declaration::Declaration as FunctionDeclaration; +use crate::eravm::context::Context; +use crate::eravm::Dependency; +use crate::eravm::WriteLLVM; + +use self::default_call::DefaultCall; +use self::deployer_call::DeployerCall; + +/// +/// The front-end runtime functions. +/// +#[derive(Debug, Clone)] +pub struct Runtime { + /// The address space where the calldata is allocated. + /// Solidity uses the ordinary heap. Vyper uses the auxiliary heap. + address_space: AddressSpace, +} + +impl Runtime { + /// The main entry function name. + pub const FUNCTION_ENTRY: &'static str = "__entry"; + + /// The deploy code function name. + pub const FUNCTION_DEPLOY_CODE: &'static str = "__deploy"; + + /// The runtime code function name. + pub const FUNCTION_RUNTIME_CODE: &'static str = "__runtime"; + + /// + /// A shortcut constructor. + /// + pub fn new(address_space: AddressSpace) -> Self { + Self { address_space } + } + + /// + /// Returns the corresponding runtime function. + /// + pub fn default_call<'ctx, D>( + context: &Context<'ctx, D>, + call_function: FunctionDeclaration<'ctx>, + ) -> FunctionDeclaration<'ctx> + where + D: Dependency + Clone, + { + context + .get_function(DefaultCall::name(call_function).as_str()) + .expect("Always exists") + .borrow() + .declaration() + } + + /// + /// Returns the corresponding runtime function. + /// + pub fn deployer_call<'ctx, D>(context: &Context<'ctx, D>) -> FunctionDeclaration<'ctx> + where + D: Dependency + Clone, + { + context + .get_function(DeployerCall::FUNCTION_NAME) + .expect("Always exists") + .borrow() + .declaration() + } +} + +impl WriteLLVM for Runtime +where + D: Dependency + Clone, +{ + fn declare(&mut self, context: &mut Context) -> anyhow::Result<()> { + //DefaultCall::new(context.llvm_runtime().far_call).declare(context)?; + DefaultCall::new(context.llvm_runtime().static_call).declare(context)?; + DefaultCall::new(context.llvm_runtime().delegate_call).declare(context)?; + DeployerCall::new(self.address_space).declare(context)?; + + Ok(()) + } + + fn into_llvm(self, context: &mut Context) -> anyhow::Result<()> { + //DefaultCall::new(context.llvm_runtime().far_call).into_llvm(context)?; + DefaultCall::new(context.llvm_runtime().static_call).into_llvm(context)?; + DefaultCall::new(context.llvm_runtime().delegate_call).into_llvm(context)?; + DeployerCall::new(self.address_space).into_llvm(context)?; + + Ok(()) + } +} diff --git a/crates/llvm-context/src/eravm/context/function/runtime/runtime_code.rs b/crates/llvm-context/src/eravm/context/function/runtime/runtime_code.rs new file mode 100644 index 0000000..dcb49b1 --- /dev/null +++ b/crates/llvm-context/src/eravm/context/function/runtime/runtime_code.rs @@ -0,0 +1,86 @@ +//! +//! The runtime code function. +//! + +use std::marker::PhantomData; + +use crate::eravm::context::code_type::CodeType; +use crate::eravm::context::function::runtime::Runtime; +use crate::eravm::context::Context; +use crate::eravm::Dependency; +use crate::eravm::WriteLLVM; + +/// +/// The runtime code function. +/// +/// Is a special function that is only used by the front-end generated code. +/// +#[derive(Debug)] +pub struct RuntimeCode +where + B: WriteLLVM, + D: Dependency + Clone, +{ + /// The runtime code AST representation. + inner: B, + /// The `D` phantom data. + _pd: PhantomData, +} + +impl RuntimeCode +where + B: WriteLLVM, + D: Dependency + Clone, +{ + /// + /// A shortcut constructor. + /// + pub fn new(inner: B) -> Self { + Self { + inner, + _pd: PhantomData, + } + } +} + +impl WriteLLVM for RuntimeCode +where + B: WriteLLVM, + D: Dependency + Clone, +{ + fn declare(&mut self, context: &mut Context) -> anyhow::Result<()> { + let function_type = + context.function_type::(vec![], 0, false); + context.add_function( + Runtime::FUNCTION_RUNTIME_CODE, + function_type, + 0, + Some(inkwell::module::Linkage::External), + )?; + + self.inner.declare(context) + } + + fn into_llvm(self, context: &mut Context) -> anyhow::Result<()> { + context.set_current_function(Runtime::FUNCTION_RUNTIME_CODE)?; + + context.set_basic_block(context.current_function().borrow().entry_block()); + context.set_code_type(CodeType::Runtime); + self.inner.into_llvm(context)?; + match context + .basic_block() + .get_last_instruction() + .map(|instruction| instruction.get_opcode()) + { + Some(inkwell::values::InstructionOpcode::Br) => {} + Some(inkwell::values::InstructionOpcode::Switch) => {} + _ => context + .build_unconditional_branch(context.current_function().borrow().return_block()), + } + + context.set_basic_block(context.current_function().borrow().return_block()); + context.build_unreachable(); + + Ok(()) + } +} diff --git a/crates/llvm-context/src/eravm/context/function/vyper_data.rs b/crates/llvm-context/src/eravm/context/function/vyper_data.rs new file mode 100644 index 0000000..fa4729a --- /dev/null +++ b/crates/llvm-context/src/eravm/context/function/vyper_data.rs @@ -0,0 +1,52 @@ +//! +//! The LLVM function Vyper data. +//! + +use std::collections::HashMap; + +/// +/// The LLVM function Vyper data. +/// +/// Describes some data that is only relevant to Vyper. +/// +#[derive(Debug)] +pub struct VyperData { + /// The block-local variables. They are still allocated at the beginning of the function, + /// but their parent block must be known in order to pass the implicit arguments thereto. + /// Is only used by the Vyper LLL IR compiler. + label_arguments: HashMap>, +} + +impl Default for VyperData { + fn default() -> Self { + Self { + label_arguments: HashMap::with_capacity(Self::LABEL_ARGUMENTS_HASHMAP_INITIAL_CAPACITY), + } + } +} + +impl VyperData { + /// The label arguments hashmap default capacity. + const LABEL_ARGUMENTS_HASHMAP_INITIAL_CAPACITY: usize = 16; + + /// + /// A shortcut constructor. + /// + pub fn new() -> Self { + Self::default() + } + + /// + /// Returns the list of a Vyper label arguments. + /// + pub fn label_arguments(&self, label_name: &str) -> Option> { + self.label_arguments.get(label_name).cloned() + } + + /// + /// Inserts arguments for the specified label. + /// + pub fn insert_label_arguments(&mut self, label_name: String, arguments: Vec) { + self.label_arguments.insert(label_name, arguments); + } +} diff --git a/crates/llvm-context/src/eravm/context/function/yul_data.rs b/crates/llvm-context/src/eravm/context/function/yul_data.rs new file mode 100644 index 0000000..4191e05 --- /dev/null +++ b/crates/llvm-context/src/eravm/context/function/yul_data.rs @@ -0,0 +1,53 @@ +//! +//! The LLVM function Yul data. +//! + +use std::collections::HashMap; + +use num::BigUint; + +/// +/// The LLVM function Yul data. +/// +/// Describes some data that is only relevant to Yul. +/// +#[derive(Debug)] +pub struct YulData { + /// The constants saved to variables. Used for peculiar cases like call simulation. + /// It is a partial implementation of the constant propagation. + constants: HashMap, +} + +impl Default for YulData { + fn default() -> Self { + Self { + constants: HashMap::with_capacity(Self::CONSTANTS_HASHMAP_INITIAL_CAPACITY), + } + } +} + +impl YulData { + /// The constants hashmap default capacity. + const CONSTANTS_HASHMAP_INITIAL_CAPACITY: usize = 16; + + /// + /// A shortcut constructor. + /// + pub fn new() -> Self { + Self::default() + } + + /// + /// Returns a constant if it has been saved. + /// + pub fn get_constant(&self, name: &str) -> Option { + self.constants.get(name).cloned() + } + + /// + /// Saves a constant detected with the partial constant propagation. + /// + pub fn insert_constant(&mut self, name: String, value: BigUint) { + self.constants.insert(name, value); + } +} diff --git a/crates/llvm-context/src/eravm/context/global.rs b/crates/llvm-context/src/eravm/context/global.rs new file mode 100644 index 0000000..0c88b05 --- /dev/null +++ b/crates/llvm-context/src/eravm/context/global.rs @@ -0,0 +1,63 @@ +//! +//! The LLVM global value. +//! + +use inkwell::types::BasicType; +use inkwell::values::BasicValue; + +use crate::eravm::context::address_space::AddressSpace; +use crate::eravm::context::Context; +use crate::EraVMDependency; + +/// +/// The LLVM global value. +/// +#[derive(Debug, Clone, Copy)] +pub struct Global<'ctx> { + /// The global type. + pub r#type: inkwell::types::BasicTypeEnum<'ctx>, + /// The global value. + pub value: inkwell::values::GlobalValue<'ctx>, +} + +impl<'ctx> Global<'ctx> { + /// + /// A shortcut constructor. + /// + pub fn new( + context: &mut Context<'ctx, D>, + r#type: T, + address_space: AddressSpace, + initializer: V, + name: &str, + ) -> Self + where + D: EraVMDependency + Clone, + T: BasicType<'ctx>, + V: BasicValue<'ctx>, + { + let r#type = r#type.as_basic_type_enum(); + + let value = context + .module() + .add_global(r#type, Some(address_space.into()), name); + let global = Self { r#type, value }; + + global.value.set_linkage(inkwell::module::Linkage::Private); + global + .value + .set_visibility(inkwell::GlobalVisibility::Default); + global.value.set_externally_initialized(false); + if let AddressSpace::Code = address_space { + global.value.set_constant(true); + } + if !r#type.is_pointer_type() { + global.value.set_initializer(&initializer); + } else { + global.value.set_initializer(&r#type.const_zero()); + context.build_store(global.into(), initializer).unwrap(); + } + + global + } +} diff --git a/crates/llvm-context/src/eravm/context/loop.rs b/crates/llvm-context/src/eravm/context/loop.rs new file mode 100644 index 0000000..d0daa7e --- /dev/null +++ b/crates/llvm-context/src/eravm/context/loop.rs @@ -0,0 +1,33 @@ +//! +//! The LLVM IR generator loop. +//! + +/// +/// The LLVM IR generator loop. +/// +#[derive(Debug, Clone)] +pub struct Loop<'ctx> { + /// The loop current block. + pub body_block: inkwell::basic_block::BasicBlock<'ctx>, + /// The increment block before the body. + pub continue_block: inkwell::basic_block::BasicBlock<'ctx>, + /// The join block after the body. + pub join_block: inkwell::basic_block::BasicBlock<'ctx>, +} + +impl<'ctx> Loop<'ctx> { + /// + /// A shortcut constructor. + /// + pub fn new( + body_block: inkwell::basic_block::BasicBlock<'ctx>, + continue_block: inkwell::basic_block::BasicBlock<'ctx>, + join_block: inkwell::basic_block::BasicBlock<'ctx>, + ) -> Self { + Self { + body_block, + continue_block, + join_block, + } + } +} diff --git a/crates/llvm-context/src/eravm/context/mod.rs b/crates/llvm-context/src/eravm/context/mod.rs new file mode 100644 index 0000000..ba6f13b --- /dev/null +++ b/crates/llvm-context/src/eravm/context/mod.rs @@ -0,0 +1,1730 @@ +//! +//! The LLVM IR generator context. +//! + +pub mod address_space; +pub mod argument; +pub mod attribute; +pub mod build; +pub mod code_type; +// pub mod debug_info; +pub mod evmla_data; +pub mod function; +pub mod global; +pub mod r#loop; +pub mod pointer; +pub mod solidity_data; +pub mod vyper_data; +pub mod yul_data; + +#[cfg(test)] +mod tests; + +use std::cell::RefCell; +use std::collections::HashMap; +use std::rc::Rc; + +use inkwell::types::BasicType; +use inkwell::values::BasicValue; + +use crate::eravm::DebugConfig; +use crate::eravm::Dependency; +use crate::optimizer::settings::Settings as OptimizerSettings; +use crate::optimizer::Optimizer; +use crate::target_machine::target::Target; +use crate::target_machine::TargetMachine; + +use self::address_space::AddressSpace; +use self::attribute::Attribute; +use self::build::Build; +use self::code_type::CodeType; +// use self::debug_info::DebugInfo; +use self::evmla_data::EVMLAData; +use self::function::declaration::Declaration as FunctionDeclaration; +use self::function::intrinsics::Intrinsics; +use self::function::llvm_runtime::LLVMRuntime; +use self::function::r#return::Return as FunctionReturn; +use self::function::Function; +use self::global::Global; +use self::pointer::Pointer; +use self::r#loop::Loop; +use self::solidity_data::SolidityData; +use self::vyper_data::VyperData; +use self::yul_data::YulData; + +/// +/// The LLVM IR generator context. +/// +/// It is a not-so-big god-like object glueing all the compilers' complexity and act as an adapter +/// and a superstructure over the inner `inkwell` LLVM context. +/// +pub struct Context<'ctx, D> +where + D: Dependency + Clone, +{ + /// The inner LLVM context. + llvm: &'ctx inkwell::context::Context, + /// The inner LLVM context builder. + builder: inkwell::builder::Builder<'ctx>, + /// The optimization tools. + optimizer: Optimizer, + /// The current module. + module: inkwell::module::Module<'ctx>, + /// The current contract code type, which can be deploy or runtime. + code_type: Option, + /// The global variables. + globals: HashMap>, + /// The LLVM intrinsic functions, defined on the LLVM side. + intrinsics: Intrinsics<'ctx>, + /// The LLVM runtime functions, defined on the LLVM side. + llvm_runtime: LLVMRuntime<'ctx>, + /// The declared functions. + functions: HashMap>>>, + /// The current active function. + current_function: Option>>>, + /// The loop context stack. + loop_stack: Vec>, + + /// The project dependency manager. It can be any entity implementing the trait. + /// The manager is used to get information about contracts and their dependencies during + /// the multi-threaded compilation process. + dependency_manager: Option, + /// Whether to append the metadata hash at the end of bytecode. + include_metadata_hash: bool, + /// The debug info of the current module. + // debug_info: DebugInfo<'ctx>, + /// The debug configuration telling whether to dump the needed IRs. + debug_config: Option, + + /// The Solidity data. + solidity_data: Option, + /// The Yul data. + yul_data: Option, + /// The EVM legacy assembly data. + evmla_data: Option>, + /// The Vyper data. + vyper_data: Option, +} + +impl<'ctx, D> Context<'ctx, D> +where + D: Dependency + Clone, +{ + /// The functions hashmap default capacity. + const FUNCTIONS_HASHMAP_INITIAL_CAPACITY: usize = 64; + + /// The globals hashmap default capacity. + const GLOBALS_HASHMAP_INITIAL_CAPACITY: usize = 4; + + /// The loop stack default capacity. + const LOOP_STACK_INITIAL_CAPACITY: usize = 16; + + /// Link in the stdlib module. + fn link_stdlib_module( + llvm: &'ctx inkwell::context::Context, + module: &inkwell::module::Module<'ctx>, + ) { + module + .link_in_module(revive_stdlib::module(llvm, "revive_stdlib").unwrap()) + .expect("the stdlib module should be linkable"); + } + + /// Link in the PolkaVM guest module, containing imported and exported functions, + /// and marking them as external (they need to be relocatable as too). + fn link_polkavm_guest_module( + llvm: &'ctx inkwell::context::Context, + module: &inkwell::module::Module<'ctx>, + ) { + module + .link_in_module(pallet_contracts_pvm_llapi::module(llvm, "polkavm_guest").unwrap()) + .expect("the PolkaVM guest API module should be linkable"); + + let call_function = module.get_function("call").unwrap(); + call_function.add_attribute( + inkwell::attributes::AttributeLoc::Function, + llvm.create_enum_attribute(Attribute::NoReturn as u32, 0), + ); + assert!(call_function.get_first_basic_block().is_none()); + + let deploy_function = module.get_function("deploy").unwrap(); + deploy_function.add_attribute( + inkwell::attributes::AttributeLoc::Function, + llvm.create_enum_attribute(Attribute::NoReturn as u32, 0), + ); + assert!(deploy_function.get_first_basic_block().is_none()); + + // TODO: Factor out a list + // Also should be prefixed with double underscores + for name in ["seal_return", "input", "set_storage", "get_storage"] { + let runtime_api_function = module.get_function(name).expect("should be declared"); + runtime_api_function.set_linkage(inkwell::module::Linkage::External); + } + } + + /// PolkaVM wants PIE code; we set this flag on the module here. + fn set_module_flags( + llvm: &'ctx inkwell::context::Context, + module: &inkwell::module::Module<'ctx>, + ) { + module.add_basic_value_flag( + "PIE Level", + inkwell::module::FlagBehavior::Override, + llvm.i32_type().const_int(2, false), + ); + } + + /// + /// Initializes a new LLVM context. + /// + pub fn new( + llvm: &'ctx inkwell::context::Context, + module: inkwell::module::Module<'ctx>, + optimizer: Optimizer, + dependency_manager: Option, + include_metadata_hash: bool, + debug_config: Option, + ) -> Self { + Self::link_stdlib_module(llvm, &module); + Self::link_polkavm_guest_module(llvm, &module); + Self::set_module_flags(llvm, &module); + + let intrinsics = Intrinsics::new(llvm, &module); + let llvm_runtime = LLVMRuntime::new(llvm, &module, &optimizer); + + Self { + llvm, + builder: llvm.create_builder(), + optimizer, + module, + code_type: None, + globals: HashMap::with_capacity(Self::GLOBALS_HASHMAP_INITIAL_CAPACITY), + intrinsics, + llvm_runtime, + functions: HashMap::with_capacity(Self::FUNCTIONS_HASHMAP_INITIAL_CAPACITY), + current_function: None, + loop_stack: Vec::with_capacity(Self::LOOP_STACK_INITIAL_CAPACITY), + + dependency_manager, + include_metadata_hash, + // debug_info, + debug_config, + + solidity_data: None, + yul_data: None, + evmla_data: None, + vyper_data: None, + } + } + + /// + /// Builds the LLVM IR module, returning the build artifacts. + /// + pub fn build( + mut self, + contract_path: &str, + metadata_hash: Option<[u8; era_compiler_common::BYTE_LENGTH_FIELD]>, + ) -> anyhow::Result { + let module_clone = self.module.clone(); + + let target_machine = TargetMachine::new(Target::PVM, self.optimizer.settings())?; + target_machine.set_target_data(self.module()); + + if let Some(ref debug_config) = self.debug_config { + debug_config.dump_llvm_ir_unoptimized(contract_path, self.module())?; + } + self.verify().map_err(|error| { + anyhow::anyhow!( + "The contract `{}` unoptimized LLVM IR verification error: {}", + contract_path, + error + ) + })?; + + self.optimizer + .run(&target_machine, self.module()) + .map_err(|error| { + anyhow::anyhow!( + "The contract `{}` optimizing error: {}", + contract_path, + error + ) + })?; + if let Some(ref debug_config) = self.debug_config { + debug_config.dump_llvm_ir_optimized(contract_path, self.module())?; + } + self.verify().map_err(|error| { + anyhow::anyhow!( + "The contract `{}` optimized LLVM IR verification error: {}", + contract_path, + error + ) + })?; + + let buffer = target_machine + .write_to_memory_buffer(self.module()) + .map_err(|error| { + anyhow::anyhow!( + "The contract `{}` assembly generating error: {}", + contract_path, + error + ) + })?; + + let assembly_text = revive_linker::link(buffer.as_slice()).map(hex::encode)?; + + let build = match crate::eravm::build_assembly_text( + contract_path, + assembly_text.as_str(), + metadata_hash, + self.debug_config(), + ) { + Ok(build) => build, + Err(_error) + if self.optimizer.settings() != &OptimizerSettings::size() + && self.optimizer.settings().is_fallback_to_size_enabled() => + { + self.optimizer = Optimizer::new(OptimizerSettings::size()); + self.module = module_clone; + self.build(contract_path, metadata_hash)? + } + Err(error) => Err(error)?, + }; + + Ok(build) + } + + /// + /// Verifies the current LLVM IR module. + /// + pub fn verify(&self) -> anyhow::Result<()> { + self.module() + .verify() + .map_err(|error| anyhow::anyhow!(error.to_string())) + } + + /// + /// Returns the inner LLVM context. + /// + pub fn llvm(&self) -> &'ctx inkwell::context::Context { + self.llvm + } + + /// + /// Returns the LLVM IR builder. + /// + pub fn builder(&self) -> &inkwell::builder::Builder<'ctx> { + &self.builder + } + + /// + /// Returns the current LLVM IR module reference. + /// + pub fn module(&self) -> &inkwell::module::Module<'ctx> { + &self.module + } + + /// + /// Sets the current code type (deploy or runtime). + /// + pub fn set_code_type(&mut self, code_type: CodeType) { + self.code_type = Some(code_type); + } + + /// + /// Returns the current code type (deploy or runtime). + /// + pub fn code_type(&self) -> Option { + self.code_type.to_owned() + } + + /// + /// Returns the pointer to a global variable. + /// + pub fn get_global(&self, name: &str) -> anyhow::Result> { + match self.globals.get(name) { + Some(global) => Ok(*global), + None => anyhow::bail!("Global variable {} is not declared", name), + } + } + + /// + /// Returns the value of a global variable. + /// + pub fn get_global_value( + &self, + name: &str, + ) -> anyhow::Result> { + let global = self.get_global(name)?; + self.build_load(global.into(), name) + } + + /// + /// Sets the value to a global variable. + /// + pub fn set_global(&mut self, name: &str, r#type: T, address_space: AddressSpace, value: V) + where + T: BasicType<'ctx> + Clone + Copy, + V: BasicValue<'ctx> + Clone + Copy, + { + match self.globals.get(name) { + Some(global) => { + let global = *global; + self.build_store(global.into(), value).unwrap(); + } + None => { + let global = Global::new(self, r#type, address_space, value, name); + self.globals.insert(name.to_owned(), global); + } + } + } + + /// + /// Returns the LLVM intrinsics collection reference. + /// + pub fn intrinsics(&self) -> &Intrinsics<'ctx> { + &self.intrinsics + } + + /// + /// Returns the LLVM runtime function collection reference. + /// + pub fn llvm_runtime(&self) -> &LLVMRuntime<'ctx> { + &self.llvm_runtime + } + + /// Declare a function already existing in the module. + pub fn declare_extern_function( + &mut self, + name: &str, + ) -> anyhow::Result>>> { + let function = self.module().get_function(name).ok_or_else(|| { + anyhow::anyhow!("Failed to activate an undeclared function `{}`", name) + })?; + + let basic_block = self.llvm.append_basic_block(function, name); + let declaration = FunctionDeclaration::new( + self.function_type::(vec![], 0, false), + function, + ); + let function = Function::new( + name.to_owned(), + declaration, + FunctionReturn::None, + basic_block, + basic_block, + ); + Function::set_default_attributes(self.llvm, function.declaration(), &self.optimizer); + + let function = Rc::new(RefCell::new(function)); + self.functions.insert(name.to_string(), function.clone()); + + Ok(function) + } + + /// + /// Appends a function to the current module. + /// + pub fn add_function( + &mut self, + name: &str, + r#type: inkwell::types::FunctionType<'ctx>, + return_values_length: usize, + mut linkage: Option, + ) -> anyhow::Result>>> { + if Function::is_near_call_abi(name) && self.is_system_mode() { + linkage = Some(inkwell::module::Linkage::External); + } + + let value = self.module().add_function(name, r#type, linkage); + + let entry_block = self.llvm.append_basic_block(value, "entry"); + let return_block = self.llvm.append_basic_block(value, "return"); + + value.set_personality_function(self.llvm_runtime.personality.value); + + let r#return = match return_values_length { + 0 => FunctionReturn::none(), + 1 => { + self.set_basic_block(entry_block); + let pointer = self.build_alloca(self.field_type(), "return_pointer"); + FunctionReturn::primitive(pointer) + } + size if name.starts_with(Function::ZKSYNC_NEAR_CALL_ABI_PREFIX) => { + let first_argument = value.get_first_param().expect("Always exists"); + let r#type = self.structure_type(vec![self.field_type(); size].as_slice()); + let pointer = first_argument.into_pointer_value(); + FunctionReturn::compound(Pointer::new(r#type, AddressSpace::Stack, pointer), size) + } + size => { + self.set_basic_block(entry_block); + let pointer = self.build_alloca( + self.structure_type( + vec![self.field_type().as_basic_type_enum(); size].as_slice(), + ), + "return_pointer", + ); + FunctionReturn::compound(pointer, size) + } + }; + + let function = Function::new( + name.to_owned(), + FunctionDeclaration::new(r#type, value), + r#return, + entry_block, + return_block, + ); + Function::set_default_attributes(self.llvm, function.declaration(), &self.optimizer); + if Function::is_near_call_abi(function.name()) && self.is_system_mode() { + Function::set_exception_handler_attributes(self.llvm, function.declaration()); + } + + let function = Rc::new(RefCell::new(function)); + self.functions.insert(name.to_string(), function.clone()); + + Ok(function) + } + + /// + /// Returns a shared reference to the specified function. + /// + pub fn get_function(&self, name: &str) -> Option>>> { + self.functions.get(name).cloned() + } + + /// + /// Returns a shared reference to the current active function. + /// + pub fn current_function(&self) -> Rc>> { + self.current_function + .clone() + .expect("Must be declared before use") + } + + /// + /// Sets the current active function. + /// + pub fn set_current_function(&mut self, name: &str) -> anyhow::Result<()> { + let function = self.functions.get(name).cloned().ok_or_else(|| { + anyhow::anyhow!("Failed to activate an undeclared function `{}`", name) + })?; + self.current_function = Some(function); + Ok(()) + } + + /// + /// Pushes a new loop context to the stack. + /// + pub fn push_loop( + &mut self, + body_block: inkwell::basic_block::BasicBlock<'ctx>, + continue_block: inkwell::basic_block::BasicBlock<'ctx>, + join_block: inkwell::basic_block::BasicBlock<'ctx>, + ) { + self.loop_stack + .push(Loop::new(body_block, continue_block, join_block)); + } + + /// + /// Pops the current loop context from the stack. + /// + pub fn pop_loop(&mut self) { + self.loop_stack.pop(); + } + + /// + /// Returns the current loop context. + /// + pub fn r#loop(&self) -> &Loop<'ctx> { + self.loop_stack + .last() + .expect("The current context is not in a loop") + } + + /// + /// Compiles a contract dependency, if the dependency manager is set. + /// + pub fn compile_dependency(&mut self, name: &str) -> anyhow::Result { + if let Some(vyper_data) = self.vyper_data.as_mut() { + vyper_data.set_is_forwarder_used(); + } + self.dependency_manager + .to_owned() + .ok_or_else(|| anyhow::anyhow!("The dependency manager is unset")) + .and_then(|manager| { + Dependency::compile( + manager, + name, + self.optimizer.settings().to_owned(), + self.yul_data + .as_ref() + .map(|data| data.is_system_mode()) + .unwrap_or_default(), + self.include_metadata_hash, + self.debug_config.clone(), + ) + }) + } + + /// + /// Gets a full contract_path from the dependency manager. + /// + pub fn resolve_path(&self, identifier: &str) -> anyhow::Result { + self.dependency_manager + .to_owned() + .ok_or_else(|| anyhow::anyhow!("The dependency manager is unset")) + .and_then(|manager| { + let full_path = manager.resolve_path(identifier)?; + Ok(full_path) + }) + } + + /// + /// Gets a deployed library address from the dependency manager. + /// + pub fn resolve_library(&self, path: &str) -> anyhow::Result> { + self.dependency_manager + .to_owned() + .ok_or_else(|| anyhow::anyhow!("The dependency manager is unset")) + .and_then(|manager| { + let address = manager.resolve_library(path)?; + let address = self.field_const_str_hex(address.as_str()); + Ok(address) + }) + } + + /// + /// Extracts the dependency manager. + /// + pub fn take_dependency_manager(&mut self) -> D { + self.dependency_manager + .take() + .expect("The dependency manager is unset") + } + + /// + /// Returns the debug config reference. + /// + pub fn debug_config(&self) -> Option<&DebugConfig> { + self.debug_config.as_ref() + } + + /// + /// Appends a new basic block to the current function. + /// + pub fn append_basic_block(&self, name: &str) -> inkwell::basic_block::BasicBlock<'ctx> { + self.llvm + .append_basic_block(self.current_function().borrow().declaration().value, name) + } + + /// + /// Sets the current basic block. + /// + pub fn set_basic_block(&self, block: inkwell::basic_block::BasicBlock<'ctx>) { + self.builder.position_at_end(block); + } + + /// + /// Returns the current basic block. + /// + pub fn basic_block(&self) -> inkwell::basic_block::BasicBlock<'ctx> { + self.builder.get_insert_block().expect("Always exists") + } + + /// + /// Builds a stack allocation instruction. + /// + /// Sets the alignment to 256 bits. + /// + pub fn build_alloca + Clone + Copy>( + &self, + r#type: T, + name: &str, + ) -> Pointer<'ctx> { + let pointer = self.builder.build_alloca(r#type, name).unwrap(); + self.basic_block() + .get_last_instruction() + .expect("Always exists") + .set_alignment(era_compiler_common::BYTE_LENGTH_FIELD as u32) + .expect("Alignment is valid"); + Pointer::new(r#type, AddressSpace::Stack, pointer) + } + + /// + /// Builds a stack load instruction. + /// + /// Sets the alignment to 256 bits for the stack and 1 bit for the heap, parent, and child. + /// + pub fn build_load( + &self, + pointer: Pointer<'ctx>, + name: &str, + ) -> anyhow::Result> { + match pointer.address_space { + AddressSpace::Heap => { + let heap_pointer = self.get_global(crate::eravm::GLOBAL_HEAP_MEMORY_POINTER)?; + + // TODO: Ensure safe casts somehow + let offset = self.builder().build_ptr_to_int( + pointer.value, + self.integer_type(32), + "offset_ptrtoint", + )?; + let pointer_value = unsafe { + self.builder + .build_gep( + self.byte_type(), + heap_pointer.value.as_pointer_value(), + &[offset], + "heap_offset_via_gep", + ) + .unwrap() + }; + let value = self + .builder() + .build_load(pointer.r#type, pointer_value, name)?; + self.basic_block() + .get_last_instruction() + .expect("Always exists") + .set_alignment(era_compiler_common::BYTE_LENGTH_BYTE as u32) + .expect("Alignment is valid"); + + Ok(self.build_byte_swap(value)) + } + AddressSpace::TransientStorage => todo!(), + AddressSpace::Storage => { + let storage_key_value = self.builder().build_ptr_to_int( + pointer.value, + self.field_type(), + "storage_ptr_to_int", + )?; + let storage_key_pointer = + self.build_alloca(storage_key_value.get_type(), "storage_key"); + let storage_key_pointer_casted = self.builder().build_ptr_to_int( + storage_key_pointer.value, + self.integer_type(32), + "storage_key_pointer_casted", + )?; + + let storage_value_pointer = self.build_alloca(self.field_type(), "storage_value"); + let storage_value_pointer_casted = self.builder().build_ptr_to_int( + storage_value_pointer.value, + self.integer_type(32), + "storage_value_pointer_casted", + )?; + + let storage_value_length_pointer = + self.build_alloca(self.integer_type(32), "out_len_ptr"); + let storage_value_length_pointer_casted = self.builder().build_ptr_to_int( + storage_value_length_pointer.value, + self.integer_type(32), + "storage_value_length_pointer_cast", + )?; + + self.builder() + .build_store(storage_key_pointer.value, storage_key_value)?; + self.builder().build_store( + storage_value_length_pointer.value, + self.integer_const(32, era_compiler_common::BIT_LENGTH_FIELD as u64), + )?; + + let runtime_api = self + .module() + .get_function("get_storage") + .expect("should be declared"); + let arguments = &[ + storage_key_pointer_casted.into(), + self.integer_const(32, 32).into(), + storage_value_pointer_casted.into(), + storage_value_length_pointer_casted.into(), + ]; + let _ = self + .builder() + .build_call(runtime_api, arguments, "call_seal_get_storage")? + .try_as_basic_value() + .left() + .expect("should not be a void function type"); + // TODO check return value + + self.build_load(storage_value_pointer, "storage_value_load") + .map(|value| self.build_byte_swap(value)) + } + AddressSpace::Code | AddressSpace::HeapAuxiliary => todo!(), + AddressSpace::Generic => Ok(self.build_byte_swap(self.build_load( + pointer.address_space_cast(self, AddressSpace::Stack, &format!("{}_cast", name))?, + name, + )?)), + AddressSpace::Stack => { + let value = self + .builder() + .build_load(pointer.r#type, pointer.value, name)?; + + let alignment = if AddressSpace::Stack == pointer.address_space { + era_compiler_common::BYTE_LENGTH_FIELD + } else { + era_compiler_common::BYTE_LENGTH_BYTE + }; + + self.basic_block() + .get_last_instruction() + .expect("Always exists") + .set_alignment(alignment as u32) + .expect("Alignment is valid"); + Ok(value) + } + } + } + + /// + /// Builds a stack store instruction. + /// + /// Sets the alignment to 256 bits for the stack and 1 bit for the heap, parent, and child. + /// + pub fn build_store(&self, pointer: Pointer<'ctx>, value: V) -> anyhow::Result<()> + where + V: BasicValue<'ctx>, + { + match pointer.address_space { + AddressSpace::Heap => { + let heap_pointer = self + .get_global(crate::eravm::GLOBAL_HEAP_MEMORY_POINTER) + .unwrap(); + + // TODO: Ensure safe casts somehow + let offset = self.builder().build_ptr_to_int( + pointer.value, + self.integer_type(32), + "offset_ptrtoint", + )?; + let pointer_value = unsafe { + self.builder() + .build_gep( + self.byte_type(), + heap_pointer.value.as_pointer_value(), + &[offset], + "heap_offset_via_gep", + ) + .unwrap() + }; + let value = self.build_byte_swap(value.as_basic_value_enum()); + + let instruction = self.builder.build_store(pointer_value, value).unwrap(); + instruction + .set_alignment(era_compiler_common::BYTE_LENGTH_BYTE as u32) + .expect("Alignment is valid"); + } + AddressSpace::TransientStorage => todo!(), + AddressSpace::Storage => { + // TODO: Tests, factor out into dedicated functions + + let storage_key_value = self.builder().build_ptr_to_int( + pointer.value, + self.field_type(), + "storage_ptr_to_int", + )?; + let storage_key_pointer = + self.build_alloca(storage_key_value.get_type(), "storage_key"); + + let storage_value_value = self + .build_byte_swap(value.as_basic_value_enum()) + .into_int_value(); + let storage_value_pointer = + self.build_alloca(storage_value_value.get_type(), "storage_value"); + + let storage_key_pointer_casted = self.builder().build_ptr_to_int( + storage_key_pointer.value, + self.integer_type(32), + "storage_key_pointer_casted", + )?; + let storage_value_pointer_casted = self.builder().build_ptr_to_int( + storage_value_pointer.value, + self.integer_type(32), + "storage_value_pointer_casted", + )?; + + self.builder() + .build_store(storage_key_pointer.value, storage_key_value)?; + self.builder() + .build_store(storage_value_pointer.value, storage_value_value)?; + + let runtime_api = self + .module() + .get_function("set_storage") + .expect("should be declared"); + let arguments = &[ + storage_key_pointer_casted.into(), + self.integer_const(32, 32).into(), + storage_value_pointer_casted.into(), + self.integer_const(32, 32).into(), + ]; + self.builder() + .build_call(runtime_api, arguments, "call_seal_set_storage")?; + } + AddressSpace::Code | AddressSpace::HeapAuxiliary => {} + AddressSpace::Generic => self.build_store( + pointer.address_space_cast(self, AddressSpace::Stack, "cast")?, + self.build_byte_swap(value.as_basic_value_enum()), + )?, + AddressSpace::Stack => { + let instruction = self.builder.build_store(pointer.value, value).unwrap(); + instruction + .set_alignment(era_compiler_common::BYTE_LENGTH_FIELD as u32) + .expect("Alignment is valid"); + } + }; + + Ok(()) + // let instruction = self.builder.build_store(pointer.value, value).unwrap(); + + // let alignment = if AddressSpace::Stack == pointer.address_space { + // era_compiler_common::BYTE_LENGTH_FIELD + // } else { + // era_compiler_common::BYTE_LENGTH_BYTE + // }; + + // instruction + // .set_alignment(alignment as u32) + // .expect("Alignment is valid"); + } + + /// Swap the endianness of an intvalue + pub fn build_byte_swap( + &self, + value: inkwell::values::BasicValueEnum<'ctx>, + ) -> inkwell::values::BasicValueEnum<'ctx> { + self.build_call(self.intrinsics().byte_swap, &[value], "call_byte_swap") + .expect("byte_swap should return a value") + } + + /// + /// Builds a GEP instruction. + /// + pub fn build_gep( + &self, + pointer: Pointer<'ctx>, + indexes: &[inkwell::values::IntValue<'ctx>], + element_type: T, + name: &str, + ) -> Pointer<'ctx> + where + T: BasicType<'ctx>, + { + assert_ne!(pointer.address_space, AddressSpace::Storage); + assert_ne!(pointer.address_space, AddressSpace::Heap); + assert_ne!(pointer.address_space, AddressSpace::HeapAuxiliary); + assert_ne!(pointer.address_space, AddressSpace::TransientStorage); + assert_ne!(pointer.address_space, AddressSpace::Code); + + let value = unsafe { + self.builder + .build_gep(pointer.r#type, pointer.value, indexes, name) + .unwrap() + }; + Pointer::new(element_type, pointer.address_space, value) + } + + /// + /// Builds a conditional branch. + /// + /// Checks if there are no other terminators in the block. + /// + pub fn build_conditional_branch( + &self, + comparison: inkwell::values::IntValue<'ctx>, + then_block: inkwell::basic_block::BasicBlock<'ctx>, + else_block: inkwell::basic_block::BasicBlock<'ctx>, + ) -> anyhow::Result<()> { + if self.basic_block().get_terminator().is_some() { + return Ok(()); + } + + self.builder + .build_conditional_branch(comparison, then_block, else_block)?; + + Ok(()) + } + + /// + /// Builds an unconditional branch. + /// + /// Checks if there are no other terminators in the block. + /// + pub fn build_unconditional_branch( + &self, + destination_block: inkwell::basic_block::BasicBlock<'ctx>, + ) { + if self.basic_block().get_terminator().is_some() { + return; + } + + self.builder + .build_unconditional_branch(destination_block) + .unwrap(); + } + + /// + /// Builds a call. + /// + pub fn build_call( + &self, + function: FunctionDeclaration<'ctx>, + arguments: &[inkwell::values::BasicValueEnum<'ctx>], + name: &str, + ) -> Option> { + let arguments_wrapped: Vec = arguments + .iter() + .copied() + .map(inkwell::values::BasicMetadataValueEnum::from) + .collect(); + let call_site_value = self + .builder + .build_indirect_call( + function.r#type, + function.value.as_global_value().as_pointer_value(), + arguments_wrapped.as_slice(), + name, + ) + .unwrap(); + self.modify_call_site_value(arguments, call_site_value, function); + call_site_value.try_as_basic_value().left() + } + + /// + /// Builds an invoke. + /// + /// Is defaulted to a call if there is no global exception handler. + /// + pub fn build_invoke( + &self, + function: FunctionDeclaration<'ctx>, + arguments: &[inkwell::values::BasicValueEnum<'ctx>], + name: &str, + ) -> Option> { + if !self + .functions + .contains_key(Function::ZKSYNC_NEAR_CALL_ABI_EXCEPTION_HANDLER) + { + return self.build_call(function, arguments, name); + } + + let return_pointer = if let Some(r#type) = function.r#type.get_return_type() { + let pointer = self.build_alloca(r#type, "invoke_return_pointer"); + self.build_store(pointer, r#type.const_zero()).unwrap(); + Some(pointer) + } else { + None + }; + + let success_block = self.append_basic_block("invoke_success_block"); + let catch_block = self.append_basic_block("invoke_catch_block"); + let current_block = self.basic_block(); + + self.set_basic_block(catch_block); + let landing_pad_type = self.structure_type(&[ + self.byte_type() + .ptr_type(AddressSpace::Stack.into()) + .as_basic_type_enum(), + self.integer_type(era_compiler_common::BIT_LENGTH_X32) + .as_basic_type_enum(), + ]); + self.builder + .build_landing_pad( + landing_pad_type, + self.llvm_runtime.personality.value, + &[self + .byte_type() + .ptr_type(AddressSpace::Stack.into()) + .const_zero() + .as_basic_value_enum()], + false, + "invoke_catch_landing", + ) + .unwrap(); + crate::eravm::utils::throw(self); + + self.set_basic_block(current_block); + let call_site_value = self + .builder + .build_indirect_invoke( + function.r#type, + function.value.as_global_value().as_pointer_value(), + arguments, + success_block, + catch_block, + name, + ) + .unwrap(); + self.modify_call_site_value(arguments, call_site_value, function); + + self.set_basic_block(success_block); + if let (Some(return_pointer), Some(mut return_value)) = + (return_pointer, call_site_value.try_as_basic_value().left()) + { + if let Some(return_type) = function.r#type.get_return_type() { + if return_type.is_pointer_type() { + return_value = self + .builder() + .build_int_to_ptr( + return_value.into_int_value(), + return_type.into_pointer_type(), + format!("{name}_invoke_return_pointer_casted").as_str(), + ) + .unwrap() + .as_basic_value_enum(); + } + } + self.build_store(return_pointer, return_value).unwrap(); + } + return_pointer.map(|pointer| self.build_load(pointer, "invoke_result").unwrap()) + } + + /// + /// Builds an invoke of local call covered with an exception handler. + /// + /// Yul does not the exception handling, so the user can declare a special handling function + /// called (see constant `ZKSYNC_NEAR_CALL_ABI_EXCEPTION_HANDLER`. If the enclosed function + /// panics, the control flow will be transferred to the exception handler. + /// + pub fn build_invoke_near_call_abi( + &self, + _function: FunctionDeclaration<'ctx>, + _arguments: Vec>, + _name: &str, + ) -> Option> { + unimplemented!() + } + + /// + /// Builds a memory copy call. + /// + /// Sets the alignment to `1`, since all non-stack memory pages have such alignment. + /// + pub fn build_memcpy( + &self, + _function: FunctionDeclaration<'ctx>, + destination: Pointer<'ctx>, + source: Pointer<'ctx>, + size: inkwell::values::IntValue<'ctx>, + _name: &str, + ) -> anyhow::Result<()> { + let _ = self + .builder() + .build_memcpy(destination.value, 1, source.value, 1, size)?; + + Ok(()) + } + + /// + /// Builds a memory copy call for the return data. + /// + /// Sets the output length to `min(output_length, return_data_size` and calls the default + /// generic page memory copy builder. + /// + pub fn build_memcpy_return_data( + &self, + function: FunctionDeclaration<'ctx>, + destination: Pointer<'ctx>, + source: Pointer<'ctx>, + size: inkwell::values::IntValue<'ctx>, + name: &str, + ) -> anyhow::Result<()> { + let pointer_casted = self.builder.build_ptr_to_int( + source.value, + self.field_type(), + format!("{name}_pointer_casted").as_str(), + )?; + let return_data_size_shifted = self.builder.build_right_shift( + pointer_casted, + self.field_const((era_compiler_common::BIT_LENGTH_X32 * 3) as u64), + false, + format!("{name}_return_data_size_shifted").as_str(), + )?; + let return_data_size_truncated = self.builder.build_and( + return_data_size_shifted, + self.field_const(u32::MAX as u64), + format!("{name}_return_data_size_truncated").as_str(), + )?; + let is_return_data_size_lesser = self.builder.build_int_compare( + inkwell::IntPredicate::ULT, + return_data_size_truncated, + size, + format!("{name}_is_return_data_size_lesser").as_str(), + )?; + let min_size = self + .builder + .build_select( + is_return_data_size_lesser, + return_data_size_truncated, + size, + format!("{name}_min_size").as_str(), + )? + .into_int_value(); + + self.build_memcpy(function, destination, source, min_size, name)?; + + Ok(()) + } + + /// + /// Builds a return. + /// + /// Checks if there are no other terminators in the block. + /// + pub fn build_return(&self, value: Option<&dyn BasicValue<'ctx>>) { + if self.basic_block().get_terminator().is_some() { + return; + } + + self.builder.build_return(value).unwrap(); + } + + /// + /// Builds an unreachable. + /// + /// Checks if there are no other terminators in the block. + /// + pub fn build_unreachable(&self) { + if self.basic_block().get_terminator().is_some() { + return; + } + + self.builder.build_unreachable().unwrap(); + } + + /// + /// Builds a long contract exit sequence. + /// + /// The deploy code does not return the runtime code like in EVM. Instead, it returns some + /// additional contract metadata, e.g. the array of immutables. + /// The deploy code uses the auxiliary heap for the return, because otherwise it is not possible + /// to allocate memory together with the Yul allocator safely. + /// + pub fn build_exit( + &self, + flags: inkwell::values::IntValue<'ctx>, + offset: inkwell::values::IntValue<'ctx>, + length: inkwell::values::IntValue<'ctx>, + ) -> anyhow::Result<()> { + // TODO: + //let return_forward_mode = if self.code_type() == Some(CodeType::Deploy) + // && return_function == self.llvm_runtime().r#return + //{ + // zkevm_opcode_defs::RetForwardPageType::UseAuxHeap + //} else { + // zkevm_opcode_defs::RetForwardPageType::UseHeap + //}; + + let heap_pointer = self.get_global(crate::eravm::GLOBAL_HEAP_MEMORY_POINTER)?; + let offset_truncated = self.safe_truncate_int_to_i32(offset)?; + let offset_into_heap = unsafe { + self.builder().build_gep( + self.byte_type(), + heap_pointer.value.as_pointer_value(), + &[offset_truncated], + "heap_offset_via_gep", + ) + }?; + + let length_pointer = self.safe_truncate_int_to_i32(length)?; + let offset_pointer = self.builder().build_ptr_to_int( + offset_into_heap, + self.integer_type(32), + "return_data_ptr_to_int", + )?; + + self.builder().build_call( + self.module().get_function("seal_return").unwrap(), + &[flags.into(), offset_pointer.into(), length_pointer.into()], + "seal_return", + )?; + self.build_unreachable(); + + Ok(()) + } + + /// Truncate a memory offset into 32 bits, trapping if it doesn't fit. + /// + /// Pointers are represented as opaque 256 bit integer values in EVM. + /// In practice, they should never exceed a 32 bit value. However, we + /// still protect against this possibility here. + pub fn safe_truncate_int_to_i32( + &self, + value: inkwell::values::IntValue<'ctx>, + ) -> anyhow::Result> { + let truncated = self.builder().build_int_truncate_or_bit_cast( + value, + self.integer_type(32), + "offset_truncated", + )?; + let extended = self.builder().build_int_z_extend_or_bit_cast( + truncated, + self.field_type(), + "offset_extended", + )?; + let is_overflow = self.builder().build_int_compare( + inkwell::IntPredicate::NE, + value, + extended, + "compare_truncated_extended", + )?; + + let continue_block = self.append_basic_block("offset_pointer_ok"); + let trap_block = self.append_basic_block("offset_pointer_overflow"); + self.build_conditional_branch(is_overflow, trap_block, continue_block)?; + + self.set_basic_block(trap_block); + self.build_call(self.intrinsics().trap, &[], "invalid_trap"); + self.build_unreachable(); + + self.set_basic_block(continue_block); + Ok(truncated) + } + + /// + /// Writes the ABI pointer to the global variable. + /// + pub fn write_abi_pointer(&mut self, pointer: Pointer<'ctx>, global_name: &str) { + self.set_global( + global_name, + self.byte_type().ptr_type(AddressSpace::Generic.into()), + AddressSpace::Stack, + pointer.value, + ); + } + + /// + /// Writes the ABI data size to the global variable. + /// + pub fn write_abi_data_size(&mut self, _pointer: Pointer<'ctx>, _global_name: &str) { + /* + let abi_pointer_value = self + .builder() + .build_ptr_to_int(pointer.value, self.field_type(), "abi_pointer_value") + .unwrap(); + let abi_pointer_value_shifted = self + .builder() + .build_right_shift( + abi_pointer_value, + self.field_const((era_compiler_common::BIT_LENGTH_X32 * 3) as u64), + false, + "abi_pointer_value_shifted", + ) + .unwrap(); + let abi_length_value = self + .builder() + .build_and( + abi_pointer_value_shifted, + self.field_const(u32::MAX as u64), + "abi_length_value", + ) + .unwrap(); + self.set_global( + global_name, + self.field_type(), + AddressSpace::Stack, + abi_length_value, + ); + */ + } + + /// + /// Returns a boolean type constant. + /// + pub fn bool_const(&self, value: bool) -> inkwell::values::IntValue<'ctx> { + self.bool_type().const_int(u64::from(value), false) + } + + /// + /// Returns an integer type constant. + /// + pub fn integer_const(&self, bit_length: usize, value: u64) -> inkwell::values::IntValue<'ctx> { + self.integer_type(bit_length).const_int(value, false) + } + + /// + /// Returns a 256-bit field type constant. + /// + pub fn field_const(&self, value: u64) -> inkwell::values::IntValue<'ctx> { + self.field_type().const_int(value, false) + } + + /// + /// Returns a 256-bit field type undefined value. + /// + pub fn field_undef(&self) -> inkwell::values::IntValue<'ctx> { + self.field_type().get_undef() + } + + /// + /// Returns a field type constant from a decimal string. + /// + pub fn field_const_str_dec(&self, value: &str) -> inkwell::values::IntValue<'ctx> { + self.field_type() + .const_int_from_string(value, inkwell::types::StringRadix::Decimal) + .unwrap_or_else(|| panic!("Invalid string constant `{value}`")) + } + + /// + /// Returns a field type constant from a hexadecimal string. + /// + pub fn field_const_str_hex(&self, value: &str) -> inkwell::values::IntValue<'ctx> { + self.field_type() + .const_int_from_string( + value.strip_prefix("0x").unwrap_or(value), + inkwell::types::StringRadix::Hexadecimal, + ) + .unwrap_or_else(|| panic!("Invalid string constant `{value}`")) + } + + /// + /// Returns the void type. + /// + pub fn void_type(&self) -> inkwell::types::VoidType<'ctx> { + self.llvm.void_type() + } + + /// + /// Returns the boolean type. + /// + pub fn bool_type(&self) -> inkwell::types::IntType<'ctx> { + self.llvm.bool_type() + } + + /// + /// Returns the default byte type. + /// + pub fn byte_type(&self) -> inkwell::types::IntType<'ctx> { + self.llvm + .custom_width_int_type(era_compiler_common::BIT_LENGTH_BYTE as u32) + } + + /// + /// Returns the integer type of the specified bit-length. + /// + pub fn integer_type(&self, bit_length: usize) -> inkwell::types::IntType<'ctx> { + self.llvm.custom_width_int_type(bit_length as u32) + } + + /// + /// Returns the default field type. + /// + pub fn field_type(&self) -> inkwell::types::IntType<'ctx> { + self.llvm + .custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32) + } + + /// + /// Returns the array type with the specified length. + /// + pub fn array_type(&self, element_type: T, length: usize) -> inkwell::types::ArrayType<'ctx> + where + T: BasicType<'ctx>, + { + element_type.array_type(length as u32) + } + + /// + /// Returns the structure type with specified fields. + /// + pub fn structure_type(&self, field_types: &[T]) -> inkwell::types::StructType<'ctx> + where + T: BasicType<'ctx>, + { + let field_types: Vec> = + field_types.iter().map(T::as_basic_type_enum).collect(); + self.llvm.struct_type(field_types.as_slice(), false) + } + + /// + /// Returns a Yul function type with the specified arguments and number of return values. + /// + pub fn function_type( + &self, + argument_types: Vec, + return_values_size: usize, + is_near_call_abi: bool, + ) -> inkwell::types::FunctionType<'ctx> + where + T: BasicType<'ctx>, + { + let mut argument_types: Vec = argument_types + .as_slice() + .iter() + .map(T::as_basic_type_enum) + .map(inkwell::types::BasicMetadataTypeEnum::from) + .collect(); + match return_values_size { + 0 => self + .llvm + .void_type() + .fn_type(argument_types.as_slice(), false), + 1 => self.field_type().fn_type(argument_types.as_slice(), false), + size if is_near_call_abi && self.is_system_mode() => { + let return_types: Vec<_> = vec![self.field_type().as_basic_type_enum(); size]; + let return_type = self + .structure_type(return_types.as_slice()) + .ptr_type(AddressSpace::Stack.into()); + argument_types.insert(0, return_type.as_basic_type_enum().into()); + return_type.fn_type(argument_types.as_slice(), false) + } + size => self + .structure_type(vec![self.field_type().as_basic_type_enum(); size].as_slice()) + .fn_type(argument_types.as_slice(), false), + } + } + + /// + /// Modifies the call site value, setting the default attributes. + /// + /// The attributes only affect the LLVM optimizations. + /// + pub fn modify_call_site_value( + &self, + arguments: &[inkwell::values::BasicValueEnum<'ctx>], + call_site_value: inkwell::values::CallSiteValue<'ctx>, + function: FunctionDeclaration<'ctx>, + ) { + for (index, argument) in arguments.iter().enumerate() { + if argument.is_pointer_value() { + call_site_value.set_alignment_attribute( + inkwell::attributes::AttributeLoc::Param(index as u32), + era_compiler_common::BYTE_LENGTH_FIELD as u32, + ); + call_site_value.add_attribute( + inkwell::attributes::AttributeLoc::Param(index as u32), + self.llvm + .create_enum_attribute(Attribute::NoAlias as u32, 0), + ); + call_site_value.add_attribute( + inkwell::attributes::AttributeLoc::Param(index as u32), + self.llvm + .create_enum_attribute(Attribute::NoCapture as u32, 0), + ); + call_site_value.add_attribute( + inkwell::attributes::AttributeLoc::Param(index as u32), + self.llvm.create_enum_attribute(Attribute::NoFree as u32, 0), + ); + if function == self.llvm_runtime().mstore8 { + call_site_value.add_attribute( + inkwell::attributes::AttributeLoc::Param(index as u32), + self.llvm + .create_enum_attribute(Attribute::WriteOnly as u32, 0), + ); + } + if function == self.llvm_runtime().sha3 { + call_site_value.add_attribute( + inkwell::attributes::AttributeLoc::Param(index as u32), + self.llvm + .create_enum_attribute(Attribute::ReadOnly as u32, 0), + ); + } + if Some(argument.get_type()) == function.r#type.get_return_type() { + if function + .r#type + .get_return_type() + .map(|r#type| { + r#type.into_pointer_type().get_address_space() + == AddressSpace::Stack.into() + }) + .unwrap_or_default() + { + call_site_value.add_attribute( + inkwell::attributes::AttributeLoc::Param(index as u32), + self.llvm + .create_enum_attribute(Attribute::Returned as u32, 0), + ); + } + call_site_value.add_attribute( + inkwell::attributes::AttributeLoc::Param(index as u32), + self.llvm.create_enum_attribute( + Attribute::Dereferenceable as u32, + (era_compiler_common::BIT_LENGTH_FIELD * 2) as u64, + ), + ); + call_site_value.add_attribute( + inkwell::attributes::AttributeLoc::Return, + self.llvm.create_enum_attribute( + Attribute::Dereferenceable as u32, + (era_compiler_common::BIT_LENGTH_FIELD * 2) as u64, + ), + ); + } + call_site_value.add_attribute( + inkwell::attributes::AttributeLoc::Param(index as u32), + self.llvm + .create_enum_attribute(Attribute::NonNull as u32, 0), + ); + call_site_value.add_attribute( + inkwell::attributes::AttributeLoc::Param(index as u32), + self.llvm + .create_enum_attribute(Attribute::NoUndef as u32, 0), + ); + } + } + + if function + .r#type + .get_return_type() + .map(|r#type| r#type.is_pointer_type()) + .unwrap_or_default() + { + call_site_value.set_alignment_attribute( + inkwell::attributes::AttributeLoc::Return, + era_compiler_common::BYTE_LENGTH_FIELD as u32, + ); + call_site_value.add_attribute( + inkwell::attributes::AttributeLoc::Return, + self.llvm + .create_enum_attribute(Attribute::NoAlias as u32, 0), + ); + call_site_value.add_attribute( + inkwell::attributes::AttributeLoc::Return, + self.llvm + .create_enum_attribute(Attribute::NonNull as u32, 0), + ); + call_site_value.add_attribute( + inkwell::attributes::AttributeLoc::Return, + self.llvm + .create_enum_attribute(Attribute::NoUndef as u32, 0), + ); + } + } + + /// + /// Sets the Solidity data. + /// + pub fn set_solidity_data(&mut self, data: SolidityData) { + self.solidity_data = Some(data); + } + + /// + /// Returns the Solidity data reference. + /// + /// # Panics + /// If the Solidity data has not been initialized. + /// + pub fn solidity(&self) -> &SolidityData { + self.solidity_data + .as_ref() + .expect("The Solidity data must have been initialized") + } + + /// + /// Returns the Solidity data mutable reference. + /// + /// # Panics + /// If the Solidity data has not been initialized. + /// + pub fn solidity_mut(&mut self) -> &mut SolidityData { + self.solidity_data + .as_mut() + .expect("The Solidity data must have been initialized") + } + + /// + /// Sets the Yul data. + /// + pub fn set_yul_data(&mut self, data: YulData) { + self.yul_data = Some(data); + } + + /// + /// Returns the Yul data reference. + /// + /// # Panics + /// If the Yul data has not been initialized. + /// + pub fn yul(&self) -> &YulData { + self.yul_data + .as_ref() + .expect("The Yul data must have been initialized") + } + + /// + /// Returns the Yul data mutable reference. + /// + /// # Panics + /// If the Yul data has not been initialized. + /// + pub fn yul_mut(&mut self) -> &mut YulData { + self.yul_data + .as_mut() + .expect("The Yul data must have been initialized") + } + + /// + /// Sets the EVM legacy assembly data. + /// + pub fn set_evmla_data(&mut self, data: EVMLAData<'ctx>) { + self.evmla_data = Some(data); + } + + /// + /// Returns the EVM legacy assembly data reference. + /// + /// # Panics + /// If the EVM data has not been initialized. + /// + pub fn evmla(&self) -> &EVMLAData<'ctx> { + self.evmla_data + .as_ref() + .expect("The EVMLA data must have been initialized") + } + + /// + /// Returns the EVM legacy assembly data mutable reference. + /// + /// # Panics + /// If the EVM data has not been initialized. + /// + pub fn evmla_mut(&mut self) -> &mut EVMLAData<'ctx> { + self.evmla_data + .as_mut() + .expect("The EVMLA data must have been initialized") + } + + /// + /// Sets the EVM legacy assembly data. + /// + pub fn set_vyper_data(&mut self, data: VyperData) { + self.vyper_data = Some(data); + } + + /// + /// Returns the Vyper data reference. + /// + /// # Panics + /// If the Vyper data has not been initialized. + /// + pub fn vyper(&self) -> &VyperData { + self.vyper_data + .as_ref() + .expect("The Solidity data must have been initialized") + } + + /// + /// Returns the current number of immutables values in the contract. + /// + /// If the size is set manually, then it is returned. Otherwise, the number of elements in + /// the identifier-to-offset mapping tree is returned. + /// + pub fn immutables_size(&self) -> anyhow::Result { + if let Some(solidity) = self.solidity_data.as_ref() { + Ok(solidity.immutables_size()) + } else if let Some(vyper) = self.vyper_data.as_ref() { + Ok(vyper.immutables_size()) + } else { + anyhow::bail!("The immutable size data is not available"); + } + } + + /// + /// Whether the system mode is enabled. + /// + pub fn is_system_mode(&self) -> bool { + self.yul_data + .as_ref() + .map(|data| data.is_system_mode()) + .unwrap_or_default() + } +} diff --git a/crates/llvm-context/src/eravm/context/pointer.rs b/crates/llvm-context/src/eravm/context/pointer.rs new file mode 100644 index 0000000..071cb55 --- /dev/null +++ b/crates/llvm-context/src/eravm/context/pointer.rs @@ -0,0 +1,137 @@ +//! +//! The LLVM pointer. +//! + +use inkwell::types::BasicType; + +use crate::eravm::context::address_space::AddressSpace; +use crate::eravm::context::global::Global; +use crate::eravm::context::Context; +use crate::eravm::Dependency; + +/// +/// The LLVM pointer. +/// +#[derive(Debug, Clone, Copy)] +pub struct Pointer<'ctx> { + /// The pointee type. + pub r#type: inkwell::types::BasicTypeEnum<'ctx>, + /// The address space. + pub address_space: AddressSpace, + /// The pointer value. + pub value: inkwell::values::PointerValue<'ctx>, +} + +impl<'ctx> Pointer<'ctx> { + /// + /// A shortcut constructor. + /// + pub fn new( + r#type: T, + address_space: AddressSpace, + value: inkwell::values::PointerValue<'ctx>, + ) -> Self + where + T: BasicType<'ctx>, + { + Self { + r#type: r#type.as_basic_type_enum(), + address_space, + value, + } + } + + /// + /// Wraps a 256-bit primitive type pointer. + /// + pub fn new_stack_field( + context: &Context<'ctx, D>, + value: inkwell::values::PointerValue<'ctx>, + ) -> Self + where + D: Dependency + Clone, + { + Self { + r#type: context.field_type().as_basic_type_enum(), + address_space: AddressSpace::Stack, + value, + } + } + + /// + /// Creates a new pointer with the specified `offset`. + /// + pub fn new_with_offset( + context: &Context<'ctx, D>, + address_space: AddressSpace, + r#type: T, + offset: inkwell::values::IntValue<'ctx>, + name: &str, + ) -> Self + where + D: Dependency + Clone, + T: BasicType<'ctx>, + { + assert_ne!( + address_space, + AddressSpace::Stack, + "Stack pointers cannot be addressed" + ); + + let value = context + .builder + .build_int_to_ptr( + offset, + context.byte_type().ptr_type(address_space.into()), + name, + ) + .unwrap(); + Self::new(r#type, address_space, value) + } + + /// + /// Casts the pointer into another type. + /// + pub fn cast(self, r#type: T) -> Self + where + T: BasicType<'ctx>, + { + Self { + r#type: r#type.as_basic_type_enum(), + address_space: self.address_space, + value: self.value, + } + } + + pub fn address_space_cast( + self, + context: &Context<'ctx, D>, + address_space: AddressSpace, + name: &str, + ) -> anyhow::Result + where + D: Dependency + Clone, + { + let value = context.builder().build_address_space_cast( + self.value, + self.r#type.ptr_type(address_space.into()), + name, + )?; + + Ok(Self { + address_space, + value, + ..self + }) + } +} + +impl<'ctx> From> for Pointer<'ctx> { + fn from(global: Global<'ctx>) -> Self { + Self { + r#type: global.r#type, + address_space: AddressSpace::Stack, + value: global.value.as_pointer_value(), + } + } +} diff --git a/crates/llvm-context/src/eravm/context/solidity_data.rs b/crates/llvm-context/src/eravm/context/solidity_data.rs new file mode 100644 index 0000000..e036902 --- /dev/null +++ b/crates/llvm-context/src/eravm/context/solidity_data.rs @@ -0,0 +1,59 @@ +//! +//! The LLVM IR generator Solidity data. +//! + +use std::collections::BTreeMap; + +/// +/// The LLVM IR generator Solidity data. +/// +/// Describes some data that is only relevant to Solidity. +/// +#[derive(Debug, Default)] +pub struct SolidityData { + /// The immutables identifier-to-offset mapping. Is only used by Solidity due to + /// the arbitrariness of its identifiers. + immutables: BTreeMap, +} + +impl SolidityData { + /// + /// A shortcut constructor. + /// + pub fn new() -> Self { + Self::default() + } + + /// + /// Returns the current number of immutables values in the contract. + /// + pub fn immutables_size(&self) -> usize { + self.immutables.len() * era_compiler_common::BYTE_LENGTH_FIELD + } + + /// + /// Allocates memory for an immutable value in the auxiliary heap. + /// + /// If the identifier is already known, just returns its offset. + /// + pub fn allocate_immutable(&mut self, identifier: &str) -> usize { + let number_of_elements = self.immutables.len(); + let new_offset = number_of_elements * era_compiler_common::BYTE_LENGTH_FIELD; + *self + .immutables + .entry(identifier.to_owned()) + .or_insert(new_offset) + } + + /// + /// Gets the offset of the immutable value. + /// + /// If the value is not yet allocated, then it is done forcibly. + /// + pub fn get_or_allocate_immutable(&mut self, identifier: &str) -> usize { + match self.immutables.get(identifier).copied() { + Some(offset) => offset, + None => self.allocate_immutable(identifier), + } + } +} diff --git a/crates/llvm-context/src/eravm/context/tests.rs b/crates/llvm-context/src/eravm/context/tests.rs new file mode 100644 index 0000000..0b36759 --- /dev/null +++ b/crates/llvm-context/src/eravm/context/tests.rs @@ -0,0 +1,136 @@ +//! +//! The LLVM IR generator context tests. +//! + +use crate::eravm::context::attribute::Attribute; +use crate::eravm::context::Context; +use crate::eravm::DummyDependency; +use crate::optimizer::settings::Settings as OptimizerSettings; +use crate::optimizer::Optimizer; + +pub fn create_context( + llvm: &inkwell::context::Context, + optimizer_settings: OptimizerSettings, +) -> Context { + crate::eravm::initialize_target(); + + let module = llvm.create_module("test"); + let optimizer = Optimizer::new(optimizer_settings); + + Context::::new(&llvm, module, optimizer, None, true, None) +} + +#[test] +pub fn check_attribute_null_pointer_is_invalid() { + let llvm = inkwell::context::Context::create(); + let mut context = create_context(&llvm, OptimizerSettings::cycles()); + + let function = context + .add_function( + "test", + context + .field_type() + .fn_type(&[context.field_type().into()], false), + 1, + Some(inkwell::module::Linkage::External), + ) + .expect("Failed to add function"); + assert!(function + .borrow() + .declaration() + .value + .attributes(inkwell::attributes::AttributeLoc::Function) + .contains(&llvm.create_enum_attribute(Attribute::NullPointerIsValid as u32, 0))); +} + +#[test] +pub fn check_attribute_optimize_for_size_mode_3() { + let llvm = inkwell::context::Context::create(); + let mut context = create_context(&llvm, OptimizerSettings::cycles()); + + let function = context + .add_function( + "test", + context + .field_type() + .fn_type(&[context.field_type().into()], false), + 1, + Some(inkwell::module::Linkage::External), + ) + .expect("Failed to add function"); + assert!(!function + .borrow() + .declaration() + .value + .attributes(inkwell::attributes::AttributeLoc::Function) + .contains(&llvm.create_enum_attribute(Attribute::OptimizeForSize as u32, 0))); +} + +#[test] +pub fn check_attribute_optimize_for_size_mode_z() { + let llvm = inkwell::context::Context::create(); + let mut context = create_context(&llvm, OptimizerSettings::size()); + + let function = context + .add_function( + "test", + context + .field_type() + .fn_type(&[context.field_type().into()], false), + 1, + Some(inkwell::module::Linkage::External), + ) + .expect("Failed to add function"); + assert!(function + .borrow() + .declaration() + .value + .attributes(inkwell::attributes::AttributeLoc::Function) + .contains(&llvm.create_enum_attribute(Attribute::OptimizeForSize as u32, 0))); +} + +#[test] +pub fn check_attribute_min_size_mode_3() { + let llvm = inkwell::context::Context::create(); + let mut context = create_context(&llvm, OptimizerSettings::cycles()); + + let function = context + .add_function( + "test", + context + .field_type() + .fn_type(&[context.field_type().into()], false), + 1, + Some(inkwell::module::Linkage::External), + ) + .expect("Failed to add function"); + assert!(!function + .borrow() + .declaration() + .value + .attributes(inkwell::attributes::AttributeLoc::Function) + .contains(&llvm.create_enum_attribute(Attribute::MinSize as u32, 0))); +} + +#[test] +pub fn check_attribute_min_size_mode_z() { + let llvm = inkwell::context::Context::create(); + let mut context = create_context(&llvm, OptimizerSettings::size()); + + let function = context + .add_function( + "test", + context + .field_type() + .fn_type(&[context.field_type().into()], false), + 1, + Some(inkwell::module::Linkage::External), + ) + .expect("Failed to add function"); + assert!(function + .borrow() + .declaration() + .value + .attributes(inkwell::attributes::AttributeLoc::Function) + .contains(&llvm.create_enum_attribute(Attribute::MinSize as u32, 0))); +} diff --git a/crates/llvm-context/src/eravm/context/vyper_data.rs b/crates/llvm-context/src/eravm/context/vyper_data.rs new file mode 100644 index 0000000..224ebef --- /dev/null +++ b/crates/llvm-context/src/eravm/context/vyper_data.rs @@ -0,0 +1,50 @@ +//! +//! The LLVM IR generator Vyper data. +//! + +/// +/// The LLVM IR generator Vyper data. +/// +/// Describes some data that is only relevant to Vyper. +/// +#[derive(Debug)] +pub struct VyperData { + /// The immutables size tracker. Stores the size in bytes. + /// Does not take into account the size of the indexes. + immutables_size: usize, + /// Whether the contract forwarder has been used. + is_forwarder_used: bool, +} + +impl VyperData { + /// + /// A shortcut constructor. + /// + pub fn new(immutables_size: usize, is_forwarder_used: bool) -> Self { + Self { + immutables_size, + is_forwarder_used, + } + } + + /// + /// Returns the size of the immutables data of the contract. + /// + pub fn immutables_size(&self) -> usize { + self.immutables_size + } + + /// + /// Sets the forwarder usage flag. + /// + pub fn set_is_forwarder_used(&mut self) { + self.is_forwarder_used = true; + } + + /// + /// Returns the forwarder usage flag. + /// + pub fn is_forwarder_used(&self) -> bool { + self.is_forwarder_used + } +} diff --git a/crates/llvm-context/src/eravm/context/yul_data.rs b/crates/llvm-context/src/eravm/context/yul_data.rs new file mode 100644 index 0000000..022b6df --- /dev/null +++ b/crates/llvm-context/src/eravm/context/yul_data.rs @@ -0,0 +1,92 @@ +//! +//! The LLVM IR generator Yul data. +//! + +use std::collections::BTreeMap; + +use num::Zero; + +/// +/// The LLVM IR generator Yul data. +/// +/// Describes some data that is only relevant to Yul. +/// +#[derive(Debug, Default)] +pub struct YulData { + /// The system mode flag. + /// The call simulations only work if this mode is enabled. + is_system_mode: bool, + /// The list of constant arrays in the code section. + /// It is a temporary storage used until the finalization method is called. + const_arrays: BTreeMap>, +} + +impl YulData { + /// + /// A shortcut constructor. + /// + pub fn new(is_system_mode: bool) -> Self { + Self { + is_system_mode, + const_arrays: BTreeMap::new(), + } + } + + /// + /// Whether the system mode is enabled. + /// + pub fn is_system_mode(&self) -> bool { + self.is_system_mode + } + + /// + /// Declares a temporary constant array representation. + /// + pub fn const_array_declare(&mut self, index: u8, size: u16) -> anyhow::Result<()> { + if self.const_arrays.contains_key(&index) { + anyhow::bail!( + "The constant array with index {} is already declared", + index + ); + } + + self.const_arrays + .insert(index, vec![num::BigUint::zero(); size as usize]); + + Ok(()) + } + + /// + /// Sets a value in the constant array representation. + /// + pub fn const_array_set( + &mut self, + index: u8, + offset: u16, + value: num::BigUint, + ) -> anyhow::Result<()> { + let array = self.const_arrays.get_mut(&index).ok_or_else(|| { + anyhow::anyhow!("The constant array with index {} is not declared", index) + })?; + if offset >= array.len() as u16 { + anyhow::bail!( + "The constant array with index {} has size {} but the offset is {}", + index, + array.len(), + offset, + ); + } + array[offset as usize] = value; + + Ok(()) + } + + /// + /// Finalizes the constant array declaration. + /// + pub fn const_array_take(&mut self, index: u8) -> anyhow::Result> { + self.const_arrays.remove(&index).ok_or_else(|| { + anyhow::anyhow!("The constant array with index {} is not declared", index) + }) + } +} diff --git a/crates/llvm-context/src/eravm/evm/arithmetic.rs b/crates/llvm-context/src/eravm/evm/arithmetic.rs new file mode 100644 index 0000000..d8b1484 --- /dev/null +++ b/crates/llvm-context/src/eravm/evm/arithmetic.rs @@ -0,0 +1,149 @@ +//! +//! Translates the arithmetic operations. +//! + +use inkwell::values::BasicValue; + +use crate::eravm::context::Context; +use crate::eravm::Dependency; + +/// +/// Translates the arithmetic addition. +/// +pub fn addition<'ctx, D>( + context: &mut Context<'ctx, D>, + operand_1: inkwell::values::IntValue<'ctx>, + operand_2: inkwell::values::IntValue<'ctx>, +) -> anyhow::Result> +where + D: Dependency + Clone, +{ + Ok(context + .builder() + .build_int_add(operand_1, operand_2, "addition_result")? + .as_basic_value_enum()) +} + +/// +/// Translates the arithmetic subtraction. +/// +pub fn subtraction<'ctx, D>( + context: &mut Context<'ctx, D>, + operand_1: inkwell::values::IntValue<'ctx>, + operand_2: inkwell::values::IntValue<'ctx>, +) -> anyhow::Result> +where + D: Dependency + Clone, +{ + Ok(context + .builder() + .build_int_sub(operand_1, operand_2, "subtraction_result")? + .as_basic_value_enum()) +} + +/// +/// Translates the arithmetic multiplication. +/// +pub fn multiplication<'ctx, D>( + context: &mut Context<'ctx, D>, + operand_1: inkwell::values::IntValue<'ctx>, + operand_2: inkwell::values::IntValue<'ctx>, +) -> anyhow::Result> +where + D: Dependency + Clone, +{ + Ok(context + .builder() + .build_int_mul(operand_1, operand_2, "multiplication_result")? + .as_basic_value_enum()) +} + +/// +/// Translates the arithmetic division. +/// +pub fn division<'ctx, D>( + context: &mut Context<'ctx, D>, + operand_1: inkwell::values::IntValue<'ctx>, + operand_2: inkwell::values::IntValue<'ctx>, +) -> anyhow::Result> +where + D: Dependency + Clone, +{ + Ok(context + .builder() + .build_int_unsigned_div(operand_1, operand_2, "udiv")? + .into()) +} + +/// +/// Translates the arithmetic remainder. +/// +pub fn remainder<'ctx, D>( + context: &mut Context<'ctx, D>, + operand_1: inkwell::values::IntValue<'ctx>, + operand_2: inkwell::values::IntValue<'ctx>, +) -> anyhow::Result> +where + D: Dependency + Clone, +{ + Ok(context + .build_call( + context.llvm_runtime().r#mod, + &[ + operand_1.as_basic_value_enum(), + operand_2.as_basic_value_enum(), + ], + "add_mod_call", + ) + .expect("Always exists")) +} + +/// +/// Translates the signed arithmetic division. +/// +/// Two differences between the EVM and LLVM IR: +/// 1. In case of division by zero, 0 is returned. +/// 2. In case of overflow, the first argument is returned. +/// +pub fn division_signed<'ctx, D>( + context: &mut Context<'ctx, D>, + operand_1: inkwell::values::IntValue<'ctx>, + operand_2: inkwell::values::IntValue<'ctx>, +) -> anyhow::Result> +where + D: Dependency + Clone, +{ + Ok(context + .build_call( + context.llvm_runtime().sdiv, + &[ + operand_1.as_basic_value_enum(), + operand_2.as_basic_value_enum(), + ], + "add_mod_call", + ) + .expect("Always exists")) +} + +/// +/// Translates the signed arithmetic remainder. +/// +pub fn remainder_signed<'ctx, D>( + context: &mut Context<'ctx, D>, + operand_1: inkwell::values::IntValue<'ctx>, + operand_2: inkwell::values::IntValue<'ctx>, +) -> anyhow::Result> +where + D: Dependency + Clone, +{ + Ok(context + .build_call( + context.llvm_runtime().smod, + &[ + operand_1.as_basic_value_enum(), + operand_2.as_basic_value_enum(), + ], + "add_mod_call", + ) + .expect("Always exists")) +} diff --git a/crates/llvm-context/src/eravm/evm/bitwise.rs b/crates/llvm-context/src/eravm/evm/bitwise.rs new file mode 100644 index 0000000..3d60fa5 --- /dev/null +++ b/crates/llvm-context/src/eravm/evm/bitwise.rs @@ -0,0 +1,235 @@ +//! +//! Translates the bitwise operations. +//! + +use inkwell::values::BasicValue; + +use crate::eravm::context::Context; +use crate::eravm::Dependency; + +/// +/// Translates the bitwise OR. +/// +pub fn or<'ctx, D>( + context: &mut Context<'ctx, D>, + operand_1: inkwell::values::IntValue<'ctx>, + operand_2: inkwell::values::IntValue<'ctx>, +) -> anyhow::Result> +where + D: Dependency + Clone, +{ + Ok(context + .builder() + .build_or(operand_1, operand_2, "or_result")? + .as_basic_value_enum()) +} + +/// +/// Translates the bitwise XOR. +/// +pub fn xor<'ctx, D>( + context: &mut Context<'ctx, D>, + operand_1: inkwell::values::IntValue<'ctx>, + operand_2: inkwell::values::IntValue<'ctx>, +) -> anyhow::Result> +where + D: Dependency + Clone, +{ + Ok(context + .builder() + .build_xor(operand_1, operand_2, "xor_result")? + .as_basic_value_enum()) +} + +/// +/// Translates the bitwise AND. +/// +pub fn and<'ctx, D>( + context: &mut Context<'ctx, D>, + operand_1: inkwell::values::IntValue<'ctx>, + operand_2: inkwell::values::IntValue<'ctx>, +) -> anyhow::Result> +where + D: Dependency + Clone, +{ + Ok(context + .builder() + .build_and(operand_1, operand_2, "and_result")? + .as_basic_value_enum()) +} + +/// +/// Translates the bitwise shift left. +/// +pub fn shift_left<'ctx, D>( + context: &mut Context<'ctx, D>, + shift: inkwell::values::IntValue<'ctx>, + value: inkwell::values::IntValue<'ctx>, +) -> anyhow::Result> +where + D: Dependency + Clone, +{ + let overflow_block = context.append_basic_block("shift_left_overflow"); + let non_overflow_block = context.append_basic_block("shift_left_non_overflow"); + let join_block = context.append_basic_block("shift_left_join"); + + let result_pointer = context.build_alloca(context.field_type(), "shift_left_result_pointer"); + let condition_is_overflow = context.builder().build_int_compare( + inkwell::IntPredicate::UGT, + shift, + context.field_const((era_compiler_common::BIT_LENGTH_FIELD - 1) as u64), + "shift_left_is_overflow", + )?; + context.build_conditional_branch(condition_is_overflow, overflow_block, non_overflow_block)?; + + context.set_basic_block(overflow_block); + context.build_store(result_pointer, context.field_const(0))?; + context.build_unconditional_branch(join_block); + + context.set_basic_block(non_overflow_block); + let value = + context + .builder() + .build_left_shift(value, shift, "shift_left_non_overflow_result")?; + context.build_store(result_pointer, value)?; + context.build_unconditional_branch(join_block); + + context.set_basic_block(join_block); + context.build_load(result_pointer, "shift_left_result") +} + +/// +/// Translates the bitwise shift right. +/// +pub fn shift_right<'ctx, D>( + context: &mut Context<'ctx, D>, + shift: inkwell::values::IntValue<'ctx>, + value: inkwell::values::IntValue<'ctx>, +) -> anyhow::Result> +where + D: Dependency + Clone, +{ + let overflow_block = context.append_basic_block("shift_right_overflow"); + let non_overflow_block = context.append_basic_block("shift_right_non_overflow"); + let join_block = context.append_basic_block("shift_right_join"); + + let result_pointer = context.build_alloca(context.field_type(), "shift_right_result_pointer"); + let condition_is_overflow = context.builder().build_int_compare( + inkwell::IntPredicate::UGT, + shift, + context.field_const((era_compiler_common::BIT_LENGTH_FIELD - 1) as u64), + "shift_right_is_overflow", + )?; + context.build_conditional_branch(condition_is_overflow, overflow_block, non_overflow_block)?; + + context.set_basic_block(overflow_block); + context.build_store(result_pointer, context.field_const(0))?; + context.build_unconditional_branch(join_block); + + context.set_basic_block(non_overflow_block); + let value = context.builder().build_right_shift( + value, + shift, + false, + "shift_right_non_overflow_result", + )?; + context.build_store(result_pointer, value)?; + context.build_unconditional_branch(join_block); + + context.set_basic_block(join_block); + context.build_load(result_pointer, "shift_right_result") +} + +/// +/// Translates the arithmetic bitwise shift right. +/// +pub fn shift_right_arithmetic<'ctx, D>( + context: &mut Context<'ctx, D>, + shift: inkwell::values::IntValue<'ctx>, + value: inkwell::values::IntValue<'ctx>, +) -> anyhow::Result> +where + D: Dependency + Clone, +{ + let overflow_block = context.append_basic_block("shift_right_arithmetic_overflow"); + let overflow_positive_block = + context.append_basic_block("shift_right_arithmetic_overflow_positive"); + let overflow_negative_block = + context.append_basic_block("shift_right_arithmetic_overflow_negative"); + let non_overflow_block = context.append_basic_block("shift_right_arithmetic_non_overflow"); + let join_block = context.append_basic_block("shift_right_arithmetic_join"); + + let result_pointer = context.build_alloca( + context.field_type(), + "shift_right_arithmetic_result_pointer", + ); + let condition_is_overflow = context.builder().build_int_compare( + inkwell::IntPredicate::UGT, + shift, + context.field_const((era_compiler_common::BIT_LENGTH_FIELD - 1) as u64), + "shift_right_arithmetic_is_overflow", + )?; + context.build_conditional_branch(condition_is_overflow, overflow_block, non_overflow_block)?; + + context.set_basic_block(overflow_block); + let sign_bit = context.builder().build_right_shift( + value, + context.field_const((era_compiler_common::BIT_LENGTH_FIELD - 1) as u64), + false, + "shift_right_arithmetic_sign_bit", + )?; + let condition_is_negative = context.builder().build_int_truncate_or_bit_cast( + sign_bit, + context.bool_type(), + "shift_right_arithmetic_sign_bit_truncated", + )?; + context.build_conditional_branch( + condition_is_negative, + overflow_negative_block, + overflow_positive_block, + )?; + + context.set_basic_block(overflow_positive_block); + context.build_store(result_pointer, context.field_const(0))?; + context.build_unconditional_branch(join_block); + + context.set_basic_block(overflow_negative_block); + context.build_store(result_pointer, context.field_type().const_all_ones())?; + context.build_unconditional_branch(join_block); + + context.set_basic_block(non_overflow_block); + let value = context.builder().build_right_shift( + value, + shift, + true, + "shift_right_arithmetic_non_overflow_result", + )?; + context.build_store(result_pointer, value)?; + context.build_unconditional_branch(join_block); + + context.set_basic_block(join_block); + context.build_load(result_pointer, "shift_right_arithmetic_result") +} + +/// +/// Translates the `byte` instruction. +/// +pub fn byte<'ctx, D>( + context: &mut Context<'ctx, D>, + operand_1: inkwell::values::IntValue<'ctx>, + operand_2: inkwell::values::IntValue<'ctx>, +) -> anyhow::Result> +where + D: Dependency + Clone, +{ + Ok(context + .build_call( + context.llvm_runtime().byte, + &[ + operand_1.as_basic_value_enum(), + operand_2.as_basic_value_enum(), + ], + "byte_call", + ) + .expect("Always exists")) +} diff --git a/crates/llvm-context/src/eravm/evm/call.rs b/crates/llvm-context/src/eravm/evm/call.rs new file mode 100644 index 0000000..65be777 --- /dev/null +++ b/crates/llvm-context/src/eravm/evm/call.rs @@ -0,0 +1,756 @@ +//! +//! Translates a contract call. +//! + +use inkwell::values::BasicValue; + +use crate::eravm::context::address_space::AddressSpace; +use crate::eravm::context::argument::Argument; +use crate::eravm::context::function::declaration::Declaration as FunctionDeclaration; +use crate::eravm::context::function::runtime::Runtime; +use crate::eravm::context::pointer::Pointer; +use crate::eravm::context::Context; +use crate::eravm::Dependency; + +/// +/// Translates a contract call. +/// +/// If the `simulation_address` is specified, the call is substituted with another instruction +/// according to the specification. +/// +#[allow(clippy::too_many_arguments)] +pub fn default<'ctx, D>( + _context: &mut Context<'ctx, D>, + _function: FunctionDeclaration<'ctx>, + _gas: inkwell::values::IntValue<'ctx>, + _address: inkwell::values::IntValue<'ctx>, + _value: Option>, + _input_offset: inkwell::values::IntValue<'ctx>, + _input_length: inkwell::values::IntValue<'ctx>, + _output_offset: inkwell::values::IntValue<'ctx>, + _output_length: inkwell::values::IntValue<'ctx>, + _constants: Vec>, +) -> anyhow::Result> +where + D: Dependency + Clone, +{ + todo!(); + + /* + if context.is_system_mode() { + let simulation_address = constants + .get_mut(1) + .and_then(|option| option.take()) + .and_then(|value| value.to_u16()); + + match simulation_address { + Some(era_compiler_common::ERAVM_ADDRESS_TO_L1) => { + crate::eravm::extensions::call::validate_call_type( + context.llvm_runtime().far_call, + function, + "to_l1", + )?; + + let is_first = gas; + let in_0 = value.expect("Always exists"); + let in_1 = input_offset; + + return crate::eravm::extensions::general::to_l1(context, is_first, in_0, in_1); + } + Some(era_compiler_common::ERAVM_ADDRESS_CODE_ADDRESS) => { + crate::eravm::extensions::call::validate_call_type( + context.llvm_runtime().static_call, + function, + "code_address", + )?; + + return crate::eravm::extensions::general::code_source(context); + } + Some(era_compiler_common::ERAVM_ADDRESS_PRECOMPILE) => { + crate::eravm::extensions::call::validate_call_type( + context.llvm_runtime().static_call, + function, + "precompile", + )?; + + let in_0 = gas; + let gas_left = input_offset; + + return crate::eravm::extensions::general::precompile(context, in_0, gas_left); + } + Some(era_compiler_common::ERAVM_ADDRESS_META) => { + crate::eravm::extensions::call::validate_call_type( + context.llvm_runtime().static_call, + function, + "meta", + )?; + + return crate::eravm::extensions::general::meta(context); + } + Some(era_compiler_common::ERAVM_ADDRESS_MIMIC_CALL) => { + let address = gas; + let abi_data = input_offset; + let mimic = input_length; + + return crate::eravm::extensions::call::mimic( + context, + context.llvm_runtime().mimic_call, + address, + mimic, + abi_data.as_basic_value_enum(), + vec![], + ); + } + Some(era_compiler_common::ERAVM_ADDRESS_SYSTEM_MIMIC_CALL) => { + let address = gas; + let abi_data = input_offset; + let mimic = input_length; + let extra_value_1 = output_offset; + let extra_value_2 = output_length; + + return crate::eravm::extensions::call::mimic( + context, + context.llvm_runtime().mimic_call, + address, + mimic, + abi_data.as_basic_value_enum(), + vec![extra_value_1, extra_value_2], + ); + } + Some(era_compiler_common::ERAVM_ADDRESS_MIMIC_CALL_BYREF) => { + let address = gas; + let mimic = input_length; + let abi_data = context.get_global_value(crate::eravm::GLOBAL_ACTIVE_POINTER)?; + + return crate::eravm::extensions::call::mimic( + context, + context.llvm_runtime().mimic_call_byref, + address, + mimic, + abi_data.as_basic_value_enum(), + vec![], + ); + } + Some(era_compiler_common::ERAVM_ADDRESS_SYSTEM_MIMIC_CALL_BYREF) => { + let address = gas; + let mimic = input_length; + let abi_data = context.get_global_value(crate::eravm::GLOBAL_ACTIVE_POINTER)?; + let extra_value_1 = output_offset; + let extra_value_2 = output_length; + + return crate::eravm::extensions::call::mimic( + context, + context.llvm_runtime().mimic_call_byref, + address, + mimic, + abi_data, + vec![extra_value_1, extra_value_2], + ); + } + Some(era_compiler_common::ERAVM_ADDRESS_RAW_FAR_CALL) => { + let address = gas; + let abi_data = input_length; + + return crate::eravm::extensions::call::raw_far( + context, + context.llvm_runtime().modify(function, false)?, + address, + abi_data.as_basic_value_enum(), + output_offset, + output_length, + ); + } + Some(era_compiler_common::ERAVM_ADDRESS_RAW_FAR_CALL_BYREF) => { + let address = gas; + let abi_data = context.get_global_value(crate::eravm::GLOBAL_ACTIVE_POINTER)?; + + return crate::eravm::extensions::call::raw_far( + context, + context.llvm_runtime().modify(function, true)?, + address, + abi_data, + output_offset, + output_length, + ); + } + Some(era_compiler_common::ERAVM_ADDRESS_SYSTEM_CALL) => { + let address = gas; + let abi_data = input_length; + let extra_value_1 = value.expect("Always exists"); + let extra_value_2 = input_offset; + let extra_value_3 = output_offset; + let extra_value_4 = output_length; + + return crate::eravm::extensions::call::system( + context, + context.llvm_runtime().modify(function, false)?, + address, + abi_data.as_basic_value_enum(), + context.field_const(0), + context.field_const(0), + vec![extra_value_1, extra_value_2, extra_value_3, extra_value_4], + ); + } + Some(era_compiler_common::ERAVM_ADDRESS_SYSTEM_CALL_BYREF) => { + let address = gas; + let abi_data = context.get_global_value(crate::eravm::GLOBAL_ACTIVE_POINTER)?; + let extra_value_1 = value.expect("Always exists"); + let extra_value_2 = input_offset; + let extra_value_3 = output_offset; + let extra_value_4 = output_length; + + return crate::eravm::extensions::call::system( + context, + context.llvm_runtime().modify(function, true)?, + address, + abi_data, + context.field_const(0), + context.field_const(0), + vec![extra_value_1, extra_value_2, extra_value_3, extra_value_4], + ); + } + Some(era_compiler_common::ERAVM_ADDRESS_SET_CONTEXT_VALUE_CALL) => { + crate::eravm::extensions::call::validate_call_type( + context.llvm_runtime().far_call, + function, + "set_context_value", + )?; + + let value = value.expect("Always exists"); + + return crate::eravm::extensions::general::set_context_value(context, value); + } + Some(era_compiler_common::ERAVM_ADDRESS_SET_PUBDATA_PRICE) => { + crate::eravm::extensions::call::validate_call_type( + context.llvm_runtime().far_call, + function, + "set_pubdata_price", + )?; + + let price = gas; + + return crate::eravm::extensions::general::set_pubdata_price(context, price); + } + Some(era_compiler_common::ERAVM_ADDRESS_INCREMENT_TX_COUNTER) => { + crate::eravm::extensions::call::validate_call_type( + context.llvm_runtime().far_call, + function, + "increment_tx_counter", + )?; + + return crate::eravm::extensions::general::increment_tx_counter(context); + } + Some(era_compiler_common::ERAVM_ADDRESS_GET_GLOBAL_PTR_CALLDATA) => { + crate::eravm::extensions::call::validate_call_type( + context.llvm_runtime().static_call, + function, + "get_global_ptr_calldata", + )?; + + let pointer = context.get_global_value(crate::eravm::GLOBAL_CALLDATA_POINTER)?; + let value = context.builder().build_ptr_to_int( + pointer.into_pointer_value(), + context.field_type(), + "calldata_abi_integer", + )?; + return Ok(value.as_basic_value_enum()); + } + Some(era_compiler_common::ERAVM_ADDRESS_GET_GLOBAL_CALL_FLAGS) => { + crate::eravm::extensions::call::validate_call_type( + context.llvm_runtime().static_call, + function, + "get_global_call_flags", + )?; + + return context.get_global_value(crate::eravm::GLOBAL_CALL_FLAGS); + } + Some(era_compiler_common::ERAVM_ADDRESS_GET_GLOBAL_PTR_RETURN_DATA) => { + crate::eravm::extensions::call::validate_call_type( + context.llvm_runtime().static_call, + function, + "get_global_ptr_return_data", + )?; + + let pointer = context.get_global_value(crate::eravm::GLOBAL_RETURN_DATA_POINTER)?; + let value = context.builder().build_ptr_to_int( + pointer.into_pointer_value(), + context.field_type(), + "return_data_abi_integer", + )?; + return Ok(value.as_basic_value_enum()); + } + Some(era_compiler_common::ERAVM_ADDRESS_EVENT_INITIALIZE) => { + crate::eravm::extensions::call::validate_call_type( + context.llvm_runtime().far_call, + function, + "event_initialize", + )?; + + let operand_1 = gas; + let operand_2 = value.expect("Always exists"); + + return crate::eravm::extensions::general::event( + context, operand_1, operand_2, true, + ); + } + Some(era_compiler_common::ERAVM_ADDRESS_EVENT_WRITE) => { + crate::eravm::extensions::call::validate_call_type( + context.llvm_runtime().far_call, + function, + "event_initialize", + )?; + + let operand_1 = gas; + let operand_2 = value.expect("Always exists"); + + return crate::eravm::extensions::general::event( + context, operand_1, operand_2, false, + ); + } + Some(era_compiler_common::ERAVM_ADDRESS_ACTIVE_PTR_LOAD_CALLDATA) => { + crate::eravm::extensions::call::validate_call_type( + context.llvm_runtime().static_call, + function, + "active_ptr_load_calldata", + )?; + + return crate::eravm::extensions::abi::calldata_ptr_to_active(context); + } + Some(era_compiler_common::ERAVM_ADDRESS_ACTIVE_PTR_LOAD_RETURN_DATA) => { + crate::eravm::extensions::call::validate_call_type( + context.llvm_runtime().static_call, + function, + "active_ptr_load_return_data", + )?; + + return crate::eravm::extensions::abi::return_data_ptr_to_active(context); + } + Some(era_compiler_common::ERAVM_ADDRESS_ACTIVE_PTR_ADD) => { + crate::eravm::extensions::call::validate_call_type( + context.llvm_runtime().static_call, + function, + "active_ptr_add", + )?; + + let offset = gas; + + return crate::eravm::extensions::abi::active_ptr_add_assign(context, offset); + } + Some(era_compiler_common::ERAVM_ADDRESS_ACTIVE_PTR_SHRINK) => { + crate::eravm::extensions::call::validate_call_type( + context.llvm_runtime().static_call, + function, + "active_ptr_shrink", + )?; + + let offset = gas; + + return crate::eravm::extensions::abi::active_ptr_shrink_assign(context, offset); + } + Some(era_compiler_common::ERAVM_ADDRESS_ACTIVE_PTR_PACK) => { + crate::eravm::extensions::call::validate_call_type( + context.llvm_runtime().static_call, + function, + "active_ptr_pack", + )?; + + let data = gas; + + return crate::eravm::extensions::abi::active_ptr_pack_assign(context, data); + } + Some(era_compiler_common::ERAVM_ADDRESS_MULTIPLICATION_HIGH_REGISTER) => { + crate::eravm::extensions::call::validate_call_type( + context.llvm_runtime().static_call, + function, + "multiplication_high_register", + )?; + + let operand_1 = gas; + let operand_2 = input_offset; + + return crate::eravm::extensions::math::multiplication_512( + context, operand_1, operand_2, + ); + } + Some(era_compiler_common::ERAVM_ADDRESS_GET_GLOBAL_EXTRA_ABI_DATA) => { + crate::eravm::extensions::call::validate_call_type( + context.llvm_runtime().static_call, + function, + "get_global_extra_abi_data", + )?; + + let index = gas; + + return crate::eravm::extensions::abi::get_extra_abi_data(context, index); + } + Some(era_compiler_common::ERAVM_ADDRESS_ACTIVE_PTR_DATA_LOAD) => { + crate::eravm::extensions::call::validate_call_type( + context.llvm_runtime().static_call, + function, + "active_ptr_data_load", + )?; + + let offset = gas; + + return crate::eravm::extensions::abi::active_ptr_data_load(context, offset); + } + Some(era_compiler_common::ERAVM_ADDRESS_ACTIVE_PTR_DATA_SIZE) => { + crate::eravm::extensions::call::validate_call_type( + context.llvm_runtime().static_call, + function, + "active_ptr_data_size", + )?; + + return crate::eravm::extensions::abi::active_ptr_data_size(context); + } + Some(era_compiler_common::ERAVM_ADDRESS_ACTIVE_PTR_DATA_COPY) => { + crate::eravm::extensions::call::validate_call_type( + context.llvm_runtime().static_call, + function, + "active_ptr_data_copy", + )?; + + let destination_offset = gas; + let source_offset = input_offset; + let size = input_length; + + return crate::eravm::extensions::abi::active_ptr_data_copy( + context, + destination_offset, + source_offset, + size, + ); + } + Some(era_compiler_common::ERAVM_ADDRESS_CONST_ARRAY_DECLARE) => { + crate::eravm::extensions::call::validate_call_type( + context.llvm_runtime().static_call, + function, + "const_array_declare", + )?; + + let index = constants + .get_mut(0) + .and_then(|option| option.take()) + .ok_or_else(|| anyhow::anyhow!("Const array index is missing"))? + .to_u8() + .ok_or_else(|| anyhow::anyhow!("Const array index must fit into 8 bits"))?; + let size = constants + .get_mut(2) + .and_then(|option| option.take()) + .ok_or_else(|| anyhow::anyhow!("Const array size is missing"))? + .to_u16() + .ok_or_else(|| anyhow::anyhow!("Const array size must fit into 16 bits"))?; + + return crate::eravm::extensions::const_array::declare(context, index, size); + } + Some(era_compiler_common::ERAVM_ADDRESS_CONST_ARRAY_SET) => { + crate::eravm::extensions::call::validate_call_type( + context.llvm_runtime().static_call, + function, + "const_array_set", + )?; + + let index = constants + .get_mut(0) + .and_then(|option| option.take()) + .ok_or_else(|| anyhow::anyhow!("Const array index is missing"))? + .to_u8() + .ok_or_else(|| anyhow::anyhow!("Const array index must fit into 8 bits"))?; + let offset = constants + .get_mut(2) + .and_then(|option| option.take()) + .ok_or_else(|| anyhow::anyhow!("Const array offset is missing"))? + .to_u16() + .ok_or_else(|| anyhow::anyhow!("Const array offset must fit into 16 bits"))?; + let value = constants + .get_mut(4) + .and_then(|option| option.take()) + .ok_or_else(|| anyhow::anyhow!("Const array assigned value is missing"))?; + + return crate::eravm::extensions::const_array::set(context, index, offset, value); + } + Some(era_compiler_common::ERAVM_ADDRESS_CONST_ARRAY_FINALIZE) => { + crate::eravm::extensions::call::validate_call_type( + context.llvm_runtime().static_call, + function, + "const_array_finalize", + )?; + + let index = constants + .get_mut(0) + .and_then(|option| option.take()) + .ok_or_else(|| anyhow::anyhow!("Const array index is missing"))? + .to_u8() + .ok_or_else(|| anyhow::anyhow!("Const array index must fit into 8 bits"))?; + + return crate::eravm::extensions::const_array::finalize(context, index); + } + Some(era_compiler_common::ERAVM_ADDRESS_CONST_ARRAY_GET) => { + crate::eravm::extensions::call::validate_call_type( + context.llvm_runtime().static_call, + function, + "const_array_get", + )?; + + let index = constants + .get_mut(0) + .and_then(|option| option.take()) + .ok_or_else(|| anyhow::anyhow!("Const array index is missing"))? + .to_u8() + .ok_or_else(|| anyhow::anyhow!("Const array index must fit into 8 bits"))?; + let offset = input_offset; + + return crate::eravm::extensions::const_array::get(context, index, offset); + } + _ => {} + } + } + + let identity_block = context.append_basic_block("contract_call_identity_block"); + let ordinary_block = context.append_basic_block("contract_call_ordinary_block"); + let join_block = context.append_basic_block("contract_call_join_block"); + + let result_pointer = context.build_alloca(context.field_type(), "contract_call_result_pointer"); + context.build_store(result_pointer, context.field_const(0)); + + context.builder().build_switch( + address, + ordinary_block, + &[( + context.field_const(zkevm_opcode_defs::ADDRESS_IDENTITY.into()), + identity_block, + )], + )?; + + { + context.set_basic_block(identity_block); + let result = identity(context, output_offset, input_offset, output_length)?; + context.build_store(result_pointer, result); + context.build_unconditional_branch(join_block); + } + + context.set_basic_block(ordinary_block); + let result = if let Some(value) = value { + default_wrapped( + context, + function, + gas, + value, + address, + input_offset, + input_length, + output_offset, + output_length, + )? + } else { + let function = Runtime::default_call(context, function); + context + .build_call( + function, + &[ + gas.as_basic_value_enum(), + address.as_basic_value_enum(), + input_offset.as_basic_value_enum(), + input_length.as_basic_value_enum(), + output_offset.as_basic_value_enum(), + output_length.as_basic_value_enum(), + ], + "default_call", + ) + .expect("Always exists") + }; + context.build_store(result_pointer, result); + context.build_unconditional_branch(join_block); + + context.set_basic_block(join_block); + let result = context.build_load(result_pointer, "contract_call_result"); + Ok(result) + */ +} + +/// +/// Translates the Yul `linkersymbol` instruction. +/// +pub fn linker_symbol<'ctx, D>( + context: &mut Context<'ctx, D>, + mut arguments: [Argument<'ctx>; 1], +) -> anyhow::Result> +where + D: Dependency + Clone, +{ + let path = arguments[0] + .original + .take() + .ok_or_else(|| anyhow::anyhow!("Linker symbol literal is missing"))?; + + Ok(context + .resolve_library(path.as_str())? + .as_basic_value_enum()) +} + +/// +/// Generates a custom request to a system contract. +/// +pub fn request<'ctx, D>( + context: &mut Context<'ctx, D>, + address: inkwell::values::IntValue<'ctx>, + signature: &'static str, + arguments: Vec>, +) -> anyhow::Result> +where + D: Dependency + Clone, +{ + let signature_hash = crate::eravm::utils::keccak256(signature.as_bytes()); + let signature_value = context.field_const_str_hex(signature_hash.as_str()); + + let calldata_size = context.field_const( + (era_compiler_common::BYTE_LENGTH_X32 + + (era_compiler_common::BYTE_LENGTH_FIELD * arguments.len())) as u64, + ); + + let calldata_array_pointer = context.build_alloca( + context.array_type(context.field_type(), arguments.len()), + "system_request_calldata_array_pointer", + ); + for (index, argument) in arguments.into_iter().enumerate() { + let argument_pointer = context.build_gep( + calldata_array_pointer, + &[context.field_const(0), context.field_const(index as u64)], + context.field_type(), + "system_request_calldata_array_pointer", + ); + context.build_store(argument_pointer, argument)?; + } + Ok(context + .build_invoke( + context.llvm_runtime().system_request, + &[ + address.as_basic_value_enum(), + signature_value.as_basic_value_enum(), + calldata_size.as_basic_value_enum(), + calldata_array_pointer.value.as_basic_value_enum(), + ], + "system_request_call", + ) + .expect("Always exists")) +} + +/// +/// The default call wrapper, which redirects the call to the `msg.value` simulator if `msg.value` +/// is not zero. +/// +#[allow(clippy::too_many_arguments)] +fn _default_wrapped<'ctx, D>( + context: &mut Context<'ctx, D>, + function: FunctionDeclaration<'ctx>, + gas: inkwell::values::IntValue<'ctx>, + value: inkwell::values::IntValue<'ctx>, + address: inkwell::values::IntValue<'ctx>, + input_offset: inkwell::values::IntValue<'ctx>, + input_length: inkwell::values::IntValue<'ctx>, + output_offset: inkwell::values::IntValue<'ctx>, + output_length: inkwell::values::IntValue<'ctx>, +) -> anyhow::Result> +where + D: Dependency + Clone, +{ + let value_zero_block = context.append_basic_block("contract_call_value_zero_block"); + let value_non_zero_block = context.append_basic_block("contract_call_value_non_zero_block"); + let value_join_block = context.append_basic_block("contract_call_value_join_block"); + + let result_pointer = + context.build_alloca(context.field_type(), "contract_call_address_result_pointer"); + context.build_store(result_pointer, context.field_const(0))?; + let is_value_zero = context.builder().build_int_compare( + inkwell::IntPredicate::EQ, + value, + context.field_const(0), + "contract_call_is_value_zero", + )?; + context.build_conditional_branch(is_value_zero, value_zero_block, value_non_zero_block)?; + + context.set_basic_block(value_non_zero_block); + let abi_data = crate::eravm::utils::abi_data( + context, + input_offset, + input_length, + Some(gas), + AddressSpace::Heap, + true, + )?; + let result = crate::eravm::extensions::call::system( + context, + context.llvm_runtime().modify(function, false)?, + context.field_const(zkevm_opcode_defs::ADDRESS_MSG_VALUE.into()), + abi_data, + output_offset, + output_length, + vec![ + value, + address, + context.field_const(u64::from(crate::eravm::r#const::NO_SYSTEM_CALL_BIT)), + ], + )?; + context.build_store(result_pointer, result)?; + context.build_unconditional_branch(value_join_block); + + context.set_basic_block(value_zero_block); + let function = Runtime::default_call(context, function); + let result = context + .build_call( + function, + &[ + gas.as_basic_value_enum(), + address.as_basic_value_enum(), + input_offset.as_basic_value_enum(), + input_length.as_basic_value_enum(), + output_offset.as_basic_value_enum(), + output_length.as_basic_value_enum(), + ], + "default_call", + ) + .expect("Always exists"); + context.build_store(result_pointer, result)?; + context.build_unconditional_branch(value_join_block); + + context.set_basic_block(value_join_block); + context.build_load(result_pointer, "contract_call_address_result") +} + +/// +/// Generates a memory copy loop repeating the behavior of the EVM `Identity` precompile. +/// +fn _identity<'ctx, D>( + context: &mut Context<'ctx, D>, + destination: inkwell::values::IntValue<'ctx>, + source: inkwell::values::IntValue<'ctx>, + size: inkwell::values::IntValue<'ctx>, +) -> anyhow::Result> +where + D: Dependency + Clone, +{ + let destination = Pointer::new_with_offset( + context, + AddressSpace::Heap, + context.byte_type(), + destination, + "contract_call_identity_destination", + ); + let source = Pointer::new_with_offset( + context, + AddressSpace::Heap, + context.byte_type(), + source, + "contract_call_identity_source", + ); + + context.build_memcpy( + context.intrinsics().memory_copy, + destination, + source, + size, + "contract_call_memcpy_to_child", + )?; + + Ok(context.field_const(1).as_basic_value_enum()) +} diff --git a/crates/llvm-context/src/eravm/evm/calldata.rs b/crates/llvm-context/src/eravm/evm/calldata.rs new file mode 100644 index 0000000..273a62e --- /dev/null +++ b/crates/llvm-context/src/eravm/evm/calldata.rs @@ -0,0 +1,88 @@ +//! +//! Translates the calldata instructions. +//! + +use crate::eravm::context::address_space::AddressSpace; +use crate::eravm::context::pointer::Pointer; +use crate::eravm::context::Context; +use crate::eravm::Dependency; +use inkwell::types::BasicType; + +/// +/// Translates the calldata load. +/// +pub fn load<'ctx, D>( + context: &mut Context<'ctx, D>, + offset: inkwell::values::IntValue<'ctx>, +) -> anyhow::Result> +where + D: Dependency + Clone, +{ + let calldata_pointer = context + .get_global(crate::eravm::GLOBAL_CALLDATA_POINTER)? + .value + .as_pointer_value(); + let offset = context.build_gep( + Pointer::new(context.byte_type(), AddressSpace::Stack, calldata_pointer), + &[offset], + context.field_type().as_basic_type_enum(), + "calldata_pointer_with_offset", + ); + context + .build_load(offset, "calldata_value") + .map(|value| context.build_byte_swap(value)) +} + +/// +/// Translates the calldata size. +/// +pub fn size<'ctx, D>( + context: &mut Context<'ctx, D>, +) -> anyhow::Result> +where + D: Dependency + Clone, +{ + let value = context.get_global_value(crate::eravm::GLOBAL_CALLDATA_SIZE)?; + + Ok(value) +} + +/// +/// Translates the calldata copy. +/// +pub fn copy<'ctx, D>( + context: &mut Context<'ctx, D>, + destination_offset: inkwell::values::IntValue<'ctx>, + source_offset: inkwell::values::IntValue<'ctx>, + size: inkwell::values::IntValue<'ctx>, +) -> anyhow::Result<()> +where + D: Dependency + Clone, +{ + // TODO: Untested + let destination = Pointer::new_with_offset( + context, + AddressSpace::Heap, + context.byte_type(), + destination_offset, + "calldata_copy_destination_pointer", + ); + let calldata_pointer = context + .get_global(crate::eravm::GLOBAL_CALLDATA_POINTER)? + .value + .as_pointer_value(); + let source = context.build_gep( + Pointer::new(context.byte_type(), AddressSpace::Stack, calldata_pointer), + &[source_offset], + context.field_type().as_basic_type_enum(), + "calldata_pointer_with_offset", + ); + + context.build_memcpy( + context.intrinsics().memory_copy_from_generic, + destination, + source, + size, + "calldata_copy_memcpy_from_child", + ) +} diff --git a/crates/llvm-context/src/eravm/evm/comparison.rs b/crates/llvm-context/src/eravm/evm/comparison.rs new file mode 100644 index 0000000..5143361 --- /dev/null +++ b/crates/llvm-context/src/eravm/evm/comparison.rs @@ -0,0 +1,36 @@ +//! +//! Translates the comparison operations. +//! + +use inkwell::values::BasicValue; + +use crate::eravm::context::Context; +use crate::eravm::Dependency; + +/// +/// Translates the comparison operations. +/// +/// There is not difference between the EVM and LLVM IR behaviors. +/// +pub fn compare<'ctx, D>( + context: &mut Context<'ctx, D>, + operand_1: inkwell::values::IntValue<'ctx>, + operand_2: inkwell::values::IntValue<'ctx>, + operation: inkwell::IntPredicate, +) -> anyhow::Result> +where + D: Dependency + Clone, +{ + let result = context.builder().build_int_compare( + operation, + operand_1, + operand_2, + "comparison_result", + )?; + let result = context.builder().build_int_z_extend_or_bit_cast( + result, + context.field_type(), + "comparison_result_extended", + )?; + Ok(result.as_basic_value_enum()) +} diff --git a/crates/llvm-context/src/eravm/evm/context.rs b/crates/llvm-context/src/eravm/evm/context.rs new file mode 100644 index 0000000..374bee2 --- /dev/null +++ b/crates/llvm-context/src/eravm/evm/context.rs @@ -0,0 +1,189 @@ +//! +//! Translates the context getter instructions. +//! + +use crate::eravm::context::Context; +use crate::eravm::Dependency; + +/// +/// Translates the `gas_limit` instruction. +/// +pub fn gas_limit<'ctx, D>( + context: &mut Context<'ctx, D>, +) -> anyhow::Result> +where + D: Dependency + Clone, +{ + crate::eravm::evm::call::request( + context, + context.field_const(zkevm_opcode_defs::ADDRESS_SYSTEM_CONTEXT.into()), + "blockGasLimit()", + vec![], + ) +} + +/// +/// Translates the `gas_price` instruction. +/// +pub fn gas_price<'ctx, D>( + context: &mut Context<'ctx, D>, +) -> anyhow::Result> +where + D: Dependency + Clone, +{ + crate::eravm::evm::call::request( + context, + context.field_const(zkevm_opcode_defs::ADDRESS_SYSTEM_CONTEXT.into()), + "gasPrice()", + vec![], + ) +} + +/// +/// Translates the `tx.origin` instruction. +/// +pub fn origin<'ctx, D>( + context: &mut Context<'ctx, D>, +) -> anyhow::Result> +where + D: Dependency + Clone, +{ + crate::eravm::evm::call::request( + context, + context.field_const(zkevm_opcode_defs::ADDRESS_SYSTEM_CONTEXT.into()), + "origin()", + vec![], + ) +} + +/// +/// Translates the `chain_id` instruction. +/// +pub fn chain_id<'ctx, D>( + context: &mut Context<'ctx, D>, +) -> anyhow::Result> +where + D: Dependency + Clone, +{ + crate::eravm::evm::call::request( + context, + context.field_const(zkevm_opcode_defs::ADDRESS_SYSTEM_CONTEXT.into()), + "chainId()", + vec![], + ) +} + +/// +/// Translates the `block_number` instruction. +/// +pub fn block_number<'ctx, D>( + context: &mut Context<'ctx, D>, +) -> anyhow::Result> +where + D: Dependency + Clone, +{ + crate::eravm::evm::call::request( + context, + context.field_const(zkevm_opcode_defs::ADDRESS_SYSTEM_CONTEXT.into()), + "getBlockNumber()", + vec![], + ) +} + +/// +/// Translates the `block_timestamp` instruction. +/// +pub fn block_timestamp<'ctx, D>( + context: &mut Context<'ctx, D>, +) -> anyhow::Result> +where + D: Dependency + Clone, +{ + crate::eravm::evm::call::request( + context, + context.field_const(zkevm_opcode_defs::ADDRESS_SYSTEM_CONTEXT.into()), + "getBlockTimestamp()", + vec![], + ) +} + +/// +/// Translates the `block_hash` instruction. +/// +pub fn block_hash<'ctx, D>( + context: &mut Context<'ctx, D>, + index: inkwell::values::IntValue<'ctx>, +) -> anyhow::Result> +where + D: Dependency + Clone, +{ + crate::eravm::evm::call::request( + context, + context.field_const(zkevm_opcode_defs::ADDRESS_SYSTEM_CONTEXT.into()), + "getBlockHashEVM(uint256)", + vec![index], + ) +} + +/// +/// Translates the `difficulty` instruction. +/// +pub fn difficulty<'ctx, D>( + context: &mut Context<'ctx, D>, +) -> anyhow::Result> +where + D: Dependency + Clone, +{ + crate::eravm::evm::call::request( + context, + context.field_const(zkevm_opcode_defs::ADDRESS_SYSTEM_CONTEXT.into()), + "difficulty()", + vec![], + ) +} + +/// +/// Translates the `coinbase` instruction. +/// +pub fn coinbase<'ctx, D>( + context: &mut Context<'ctx, D>, +) -> anyhow::Result> +where + D: Dependency + Clone, +{ + crate::eravm::evm::call::request( + context, + context.field_const(zkevm_opcode_defs::ADDRESS_SYSTEM_CONTEXT.into()), + "coinbase()", + vec![], + ) +} + +/// +/// Translates the `basefee` instruction. +/// +pub fn basefee<'ctx, D>( + context: &mut Context<'ctx, D>, +) -> anyhow::Result> +where + D: Dependency + Clone, +{ + crate::eravm::evm::call::request( + context, + context.field_const(zkevm_opcode_defs::ADDRESS_SYSTEM_CONTEXT.into()), + "baseFee()", + vec![], + ) +} + +/// +/// Translates the `msize` instruction. +/// +pub fn msize<'ctx, D>( + _context: &mut Context<'ctx, D>, +) -> anyhow::Result> +where + D: Dependency + Clone, +{ + todo!() +} diff --git a/crates/llvm-context/src/eravm/evm/create.rs b/crates/llvm-context/src/eravm/evm/create.rs new file mode 100644 index 0000000..41252f2 --- /dev/null +++ b/crates/llvm-context/src/eravm/evm/create.rs @@ -0,0 +1,185 @@ +//! +//! Translates the contract creation instructions. +//! + +use inkwell::values::BasicValue; +use num::Zero; + +use crate::eravm::context::argument::Argument; +use crate::eravm::context::code_type::CodeType; +use crate::eravm::context::function::runtime::Runtime; +use crate::eravm::context::Context; +use crate::eravm::Dependency; + +/// +/// Translates the contract `create` instruction. +/// +/// The instruction is simulated by a call to a system contract. +/// +pub fn create<'ctx, D>( + context: &mut Context<'ctx, D>, + value: inkwell::values::IntValue<'ctx>, + input_offset: inkwell::values::IntValue<'ctx>, + input_length: inkwell::values::IntValue<'ctx>, +) -> anyhow::Result> +where + D: Dependency + Clone, +{ + let signature_hash_string = + crate::eravm::utils::keccak256(crate::eravm::DEPLOYER_SIGNATURE_CREATE.as_bytes()); + let signature_hash = context.field_const_str_hex(signature_hash_string.as_str()); + + let salt = context.field_const(0); + + let function = Runtime::deployer_call(context); + let result = context + .build_call( + function, + &[ + value.as_basic_value_enum(), + input_offset.as_basic_value_enum(), + input_length.as_basic_value_enum(), + signature_hash.as_basic_value_enum(), + salt.as_basic_value_enum(), + ], + "create_deployer_call", + ) + .expect("Always exists"); + + Ok(result) +} + +/// +/// Translates the contract `create2` instruction. +/// +/// The instruction is simulated by a call to a system contract. +/// +pub fn create2<'ctx, D>( + context: &mut Context<'ctx, D>, + value: inkwell::values::IntValue<'ctx>, + input_offset: inkwell::values::IntValue<'ctx>, + input_length: inkwell::values::IntValue<'ctx>, + salt: Option>, +) -> anyhow::Result> +where + D: Dependency + Clone, +{ + let signature_hash_string = + crate::eravm::utils::keccak256(crate::eravm::DEPLOYER_SIGNATURE_CREATE2.as_bytes()); + let signature_hash = context.field_const_str_hex(signature_hash_string.as_str()); + + let salt = salt.unwrap_or_else(|| context.field_const(0)); + + let function = Runtime::deployer_call(context); + let result = context + .build_call( + function, + &[ + value.as_basic_value_enum(), + input_offset.as_basic_value_enum(), + input_length.as_basic_value_enum(), + signature_hash.as_basic_value_enum(), + salt.as_basic_value_enum(), + ], + "create2_deployer_call", + ) + .expect("Always exists"); + + Ok(result) +} + +/// +/// Translates the contract hash instruction, which is actually used to set the hash of the contract +/// being created, or other related auxiliary data. +/// +/// Represents `dataoffset` in Yul and `PUSH [$]` in the EVM legacy assembly. +/// +pub fn contract_hash<'ctx, D>( + context: &mut Context<'ctx, D>, + identifier: String, +) -> anyhow::Result> +where + D: Dependency + Clone, +{ + let code_type = context + .code_type() + .ok_or_else(|| anyhow::anyhow!("The contract code part type is undefined"))?; + + let parent = context.module().get_name().to_str().expect("Always valid"); + + let contract_path = + context + .resolve_path(identifier.as_str()) + .map_err(|error| match code_type { + CodeType::Runtime if identifier.ends_with("_deployed") => { + anyhow::anyhow!("type({}).runtimeCode is not supported", identifier) + } + _ => error, + })?; + if contract_path.as_str() == parent { + return Ok(Argument::new_with_constant( + context.field_const(0).as_basic_value_enum(), + num::BigUint::zero(), + )); + } else if identifier.ends_with("_deployed") && code_type == CodeType::Runtime { + anyhow::bail!("type({}).runtimeCode is not supported", identifier); + } + + let hash_string = context.compile_dependency(identifier.as_str())?; + let hash_value = context + .field_const_str_hex(hash_string.as_str()) + .as_basic_value_enum(); + Ok(Argument::new_with_original(hash_value, hash_string)) +} + +/// +/// Translates the deployer call header size instruction, Usually, the header consists of: +/// - the deployer contract method signature +/// - the salt if the call is `create2`, or zero if the call is `create1` +/// - the hash of the bytecode of the contract whose instance is being created +/// - the offset of the constructor arguments +/// - the length of the constructor arguments +/// +/// If the call is `create1`, the space for the salt is still allocated, because the memory for the +/// header is allocated by the Yul or EVM legacy assembly before it is known which version of +/// `create` is going to be used. +/// +/// Represents `datasize` in Yul and `PUSH #[$]` in the EVM legacy assembly. +/// +pub fn header_size<'ctx, D>( + context: &mut Context<'ctx, D>, + identifier: String, +) -> anyhow::Result> +where + D: Dependency + Clone, +{ + let code_type = context + .code_type() + .ok_or_else(|| anyhow::anyhow!("The contract code part type is undefined"))?; + + let parent = context.module().get_name().to_str().expect("Always valid"); + + let contract_path = + context + .resolve_path(identifier.as_str()) + .map_err(|error| match code_type { + CodeType::Runtime if identifier.ends_with("_deployed") => { + anyhow::anyhow!("type({}).runtimeCode is not supported", identifier) + } + _ => error, + })?; + if contract_path.as_str() == parent { + return Ok(Argument::new_with_constant( + context.field_const(0).as_basic_value_enum(), + num::BigUint::zero(), + )); + } else if identifier.ends_with("_deployed") && code_type == CodeType::Runtime { + anyhow::bail!("type({}).runtimeCode is not supported", identifier); + } + + let size_bigint = num::BigUint::from(crate::eravm::DEPLOYER_CALL_HEADER_SIZE); + let size_value = context + .field_const(crate::eravm::DEPLOYER_CALL_HEADER_SIZE as u64) + .as_basic_value_enum(); + Ok(Argument::new_with_constant(size_value, size_bigint)) +} diff --git a/crates/llvm-context/src/eravm/evm/crypto.rs b/crates/llvm-context/src/eravm/evm/crypto.rs new file mode 100644 index 0000000..70575c4 --- /dev/null +++ b/crates/llvm-context/src/eravm/evm/crypto.rs @@ -0,0 +1,49 @@ +//! +//! Translates the cryptographic operations. +//! + +use inkwell::values::BasicValue; + +use crate::eravm::context::address_space::AddressSpace; +use crate::eravm::context::function::Function as EraVMFunction; +use crate::eravm::context::Context; +use crate::eravm::Dependency; + +/// +/// Translates the `sha3` instruction. +/// +pub fn sha3<'ctx, D>( + context: &mut Context<'ctx, D>, + offset: inkwell::values::IntValue<'ctx>, + length: inkwell::values::IntValue<'ctx>, +) -> anyhow::Result> +where + D: Dependency + Clone, +{ + Ok(offset.into()) + /* + let offset_pointer = context.builder().build_int_to_ptr( + offset, + context.byte_type().ptr_type(AddressSpace::Heap.into()), + "sha3_offset_pointer", + )?; + + Ok(context + .build_invoke( + context.llvm_runtime().sha3, + &[ + offset_pointer.as_basic_value_enum(), + length.as_basic_value_enum(), + context + .bool_const( + context + .get_function(EraVMFunction::ZKSYNC_NEAR_CALL_ABI_EXCEPTION_HANDLER) + .is_some(), + ) + .as_basic_value_enum(), + ], + "sha3_call", + ) + .expect("Always exists")) + */ +} diff --git a/crates/llvm-context/src/eravm/evm/ether_gas.rs b/crates/llvm-context/src/eravm/evm/ether_gas.rs new file mode 100644 index 0000000..d369109 --- /dev/null +++ b/crates/llvm-context/src/eravm/evm/ether_gas.rs @@ -0,0 +1,50 @@ +//! +//! Translates the value and balance operations. +//! + +use inkwell::values::BasicValue; + +use crate::eravm::context::Context; +use crate::eravm::Dependency; + +/// +/// Translates the `gas` instruction. +/// +pub fn gas<'ctx, D>( + context: &mut Context<'ctx, D>, +) -> anyhow::Result> +where + D: Dependency + Clone, +{ + Ok(context.integer_const(256, 0).as_basic_value_enum()) +} + +/// +/// Translates the `value` instruction. +/// +pub fn value<'ctx, D>( + context: &mut Context<'ctx, D>, +) -> anyhow::Result> +where + D: Dependency + Clone, +{ + Ok(context.integer_const(256, 0).as_basic_value_enum()) +} + +/// +/// Translates the `balance` instructions. +/// +pub fn balance<'ctx, D>( + context: &mut Context<'ctx, D>, + address: inkwell::values::IntValue<'ctx>, +) -> anyhow::Result> +where + D: Dependency + Clone, +{ + crate::eravm::evm::call::request( + context, + context.field_const(zkevm_opcode_defs::ADDRESS_ETH_TOKEN.into()), + "balanceOf(uint256)", + vec![address], + ) +} diff --git a/crates/llvm-context/src/eravm/evm/event.rs b/crates/llvm-context/src/eravm/evm/event.rs new file mode 100644 index 0000000..898d591 --- /dev/null +++ b/crates/llvm-context/src/eravm/evm/event.rs @@ -0,0 +1,81 @@ +//! +//! Translates a log or event call. +//! + +use inkwell::values::BasicValue; + +use crate::eravm::context::address_space::AddressSpace; +use crate::eravm::context::Context; +use crate::eravm::Dependency; + +/// +/// Translates a log or event call. +/// +/// The decoding logic is implemented in a system contract, which is called from here. +/// +/// There are several cases of the translation for the sake of efficiency, since the front-end +/// emits topics and values sequentially by one, but the LLVM intrinsic and bytecode instruction +/// accept two at once. +/// +pub fn log<'ctx, D>( + context: &mut Context<'ctx, D>, + input_offset: inkwell::values::IntValue<'ctx>, + input_length: inkwell::values::IntValue<'ctx>, + topics: Vec>, +) -> anyhow::Result<()> +where + D: Dependency + Clone, +{ + /* + let failure_block = context.append_basic_block("event_failure_block"); + let join_block = context.append_basic_block("event_join_block"); + + let gas = crate::eravm::evm::ether_gas::gas(context)?.into_int_value(); + let abi_data = crate::eravm::utils::abi_data( + context, + input_offset, + input_length, + Some(gas), + AddressSpace::Heap, + true, + )?; + let mut extra_abi_data = Vec::with_capacity(1 + topics.len()); + extra_abi_data.push(context.field_const(topics.len() as u64)); + extra_abi_data.extend(topics); + + let result = context + .build_call( + context.llvm_runtime().far_call, + crate::eravm::utils::external_call_arguments( + context, + abi_data.as_basic_value_enum(), + context.field_const(zkevm_opcode_defs::ADDRESS_EVENT_WRITER as u64), + extra_abi_data, + None, + ) + .as_slice(), + "event_writer_call_external", + ) + .expect("Always returns a value"); + + let result_status_code_boolean = context + .builder() + .build_extract_value( + result.into_struct_value(), + 1, + "event_writer_external_result_status_code_boolean", + ) + .expect("Always exists"); + context.build_conditional_branch( + result_status_code_boolean.into_int_value(), + join_block, + failure_block, + )?; + + context.set_basic_block(failure_block); + crate::eravm::evm::r#return::revert(context, context.field_const(0), context.field_const(0))?; + + context.set_basic_block(join_block); + */ + Ok(()) +} diff --git a/crates/llvm-context/src/eravm/evm/ext_code.rs b/crates/llvm-context/src/eravm/evm/ext_code.rs new file mode 100644 index 0000000..8255810 --- /dev/null +++ b/crates/llvm-context/src/eravm/evm/ext_code.rs @@ -0,0 +1,42 @@ +//! +//! Translates the external code operations. +//! + +use crate::eravm::context::Context; +use crate::eravm::Dependency; + +/// +/// Translates the `extcodesize` instruction. +/// +pub fn size<'ctx, D>( + context: &mut Context<'ctx, D>, + address: inkwell::values::IntValue<'ctx>, +) -> anyhow::Result> +where + D: Dependency + Clone, +{ + crate::eravm::evm::call::request( + context, + context.field_const(zkevm_opcode_defs::ADDRESS_ACCOUNT_CODE_STORAGE.into()), + "getCodeSize(uint256)", + vec![address], + ) +} + +/// +/// Translates the `extcodehash` instruction. +/// +pub fn hash<'ctx, D>( + context: &mut Context<'ctx, D>, + address: inkwell::values::IntValue<'ctx>, +) -> anyhow::Result> +where + D: Dependency + Clone, +{ + crate::eravm::evm::call::request( + context, + context.field_const(zkevm_opcode_defs::ADDRESS_ACCOUNT_CODE_STORAGE.into()), + "getCodeHash(uint256)", + vec![address], + ) +} diff --git a/crates/llvm-context/src/eravm/evm/immutable.rs b/crates/llvm-context/src/eravm/evm/immutable.rs new file mode 100644 index 0000000..71f9a5a --- /dev/null +++ b/crates/llvm-context/src/eravm/evm/immutable.rs @@ -0,0 +1,120 @@ +//! +//! Translates the contract immutable operations. +//! + +use crate::eravm::context::address_space::AddressSpace; +use crate::eravm::context::code_type::CodeType; +use crate::eravm::context::pointer::Pointer; +use crate::eravm::context::Context; +use crate::eravm::Dependency; + +/// +/// Translates the contract immutable load. +/// +/// In the deploy code the values are read from the auxiliary heap. +/// In the runtime code they are requested from the system contract. +/// +pub fn load<'ctx, D>( + context: &mut Context<'ctx, D>, + index: inkwell::values::IntValue<'ctx>, +) -> anyhow::Result> +where + D: Dependency + Clone, +{ + match context.code_type() { + None => { + anyhow::bail!("Immutables are not available if the contract part is undefined"); + } + Some(CodeType::Deploy) => { + let index_double = context.builder().build_int_mul( + index, + context.field_const(2), + "immutable_load_index_double", + )?; + let offset_absolute = context.builder().build_int_add( + index_double, + context.field_const( + crate::eravm::HEAP_AUX_OFFSET_CONSTRUCTOR_RETURN_DATA + + (3 * era_compiler_common::BYTE_LENGTH_FIELD) as u64, + ), + "immutable_offset_absolute", + )?; + let immutable_pointer = Pointer::new_with_offset( + context, + AddressSpace::HeapAuxiliary, + context.field_type(), + offset_absolute, + "immutable_pointer", + ); + context.build_load(immutable_pointer, "immutable_value") + } + Some(CodeType::Runtime) => { + todo!() + } + } +} + +/// +/// Translates the contract immutable store. +/// +/// In the deploy code the values are written to the auxiliary heap at the predefined offset, +/// being prepared for returning to the system contract for saving. +/// +/// Ignored in the runtime code. +/// +pub fn store<'ctx, D>( + context: &mut Context<'ctx, D>, + index: inkwell::values::IntValue<'ctx>, + value: inkwell::values::IntValue<'ctx>, +) -> anyhow::Result<()> +where + D: Dependency + Clone, +{ + match context.code_type() { + None => { + anyhow::bail!("Immutables are not available if the contract part is undefined"); + } + Some(CodeType::Deploy) => { + let index_double = context.builder().build_int_mul( + index, + context.field_const(2), + "immutable_load_index_double", + )?; + let index_offset_absolute = context.builder().build_int_add( + index_double, + context.field_const( + crate::eravm::HEAP_AUX_OFFSET_CONSTRUCTOR_RETURN_DATA + + (2 * era_compiler_common::BYTE_LENGTH_FIELD) as u64, + ), + "index_offset_absolute", + )?; + let index_offset_pointer = Pointer::new_with_offset( + context, + AddressSpace::HeapAuxiliary, + context.field_type(), + index_offset_absolute, + "immutable_index_pointer", + ); + context.build_store(index_offset_pointer, index)?; + + let value_offset_absolute = context.builder().build_int_add( + index_offset_absolute, + context.field_const(era_compiler_common::BYTE_LENGTH_FIELD as u64), + "value_offset_absolute", + )?; + let value_offset_pointer = Pointer::new_with_offset( + context, + AddressSpace::HeapAuxiliary, + context.field_type(), + value_offset_absolute, + "immutable_value_pointer", + ); + context.build_store(value_offset_pointer, value)?; + + Ok(()) + } + Some(CodeType::Runtime) => { + anyhow::bail!("Immutable writes are not available in the runtime code"); + } + } +} diff --git a/crates/llvm-context/src/eravm/evm/math.rs b/crates/llvm-context/src/eravm/evm/math.rs new file mode 100644 index 0000000..b7a41ff --- /dev/null +++ b/crates/llvm-context/src/eravm/evm/math.rs @@ -0,0 +1,98 @@ +//! +//! Translates the mathematical operations. +//! + +use inkwell::values::BasicValue; + +use crate::eravm::context::Context; +use crate::eravm::Dependency; + +/// +/// Translates the `addmod` instruction. +/// +pub fn add_mod<'ctx, D>( + context: &mut Context<'ctx, D>, + operand_1: inkwell::values::IntValue<'ctx>, + operand_2: inkwell::values::IntValue<'ctx>, + modulo: inkwell::values::IntValue<'ctx>, +) -> anyhow::Result> +where + D: Dependency + Clone, +{ + Ok(context + .build_call( + context.llvm_runtime().add_mod, + &[ + operand_1.as_basic_value_enum(), + operand_2.as_basic_value_enum(), + modulo.as_basic_value_enum(), + ], + "add_mod_call", + ) + .expect("Always exists")) +} + +/// +/// Translates the `mulmod` instruction. +/// +pub fn mul_mod<'ctx, D>( + context: &mut Context<'ctx, D>, + operand_1: inkwell::values::IntValue<'ctx>, + operand_2: inkwell::values::IntValue<'ctx>, + modulo: inkwell::values::IntValue<'ctx>, +) -> anyhow::Result> +where + D: Dependency + Clone, +{ + Ok(context + .build_call( + context.llvm_runtime().mul_mod, + &[ + operand_1.as_basic_value_enum(), + operand_2.as_basic_value_enum(), + modulo.as_basic_value_enum(), + ], + "mul_mod_call", + ) + .expect("Always exists")) +} + +/// +/// Translates the `exp` instruction. +/// +pub fn exponent<'ctx, D>( + context: &mut Context<'ctx, D>, + value: inkwell::values::IntValue<'ctx>, + exponent: inkwell::values::IntValue<'ctx>, +) -> anyhow::Result> +where + D: Dependency + Clone, +{ + Ok(context + .build_call( + context.llvm_runtime().exp, + &[value.as_basic_value_enum(), exponent.as_basic_value_enum()], + "exp_call", + ) + .expect("Always exists")) +} + +/// +/// Translates the `signextend` instruction. +/// +pub fn sign_extend<'ctx, D>( + context: &mut Context<'ctx, D>, + bytes: inkwell::values::IntValue<'ctx>, + value: inkwell::values::IntValue<'ctx>, +) -> anyhow::Result> +where + D: Dependency + Clone, +{ + Ok(context + .build_call( + context.llvm_runtime().sign_extend, + &[bytes.as_basic_value_enum(), value.as_basic_value_enum()], + "sign_extend_call", + ) + .expect("Always exists")) +} diff --git a/crates/llvm-context/src/eravm/evm/memory.rs b/crates/llvm-context/src/eravm/evm/memory.rs new file mode 100644 index 0000000..686757e --- /dev/null +++ b/crates/llvm-context/src/eravm/evm/memory.rs @@ -0,0 +1,87 @@ +//! +//! Translates the heap memory operations. +//! + +use inkwell::values::BasicValue; + +use crate::eravm::context::address_space::AddressSpace; +use crate::eravm::context::pointer::Pointer; +use crate::eravm::context::Context; +use crate::eravm::Dependency; + +/// +/// Translates the `mload` instruction. +/// +/// Uses the main heap. +/// +pub fn load<'ctx, D>( + context: &mut Context<'ctx, D>, + offset: inkwell::values::IntValue<'ctx>, +) -> anyhow::Result> +where + D: Dependency + Clone, +{ + let pointer = Pointer::new_with_offset( + context, + AddressSpace::Heap, + context.field_type(), + offset, + "memory_load_pointer", + ); + context.build_load(pointer, "memory_load_result") +} + +/// +/// Translates the `mstore` instruction. +/// +/// Uses the main heap. +/// +pub fn store<'ctx, D>( + context: &mut Context<'ctx, D>, + offset: inkwell::values::IntValue<'ctx>, + value: inkwell::values::IntValue<'ctx>, +) -> anyhow::Result<()> +where + D: Dependency + Clone, +{ + let pointer = Pointer::new_with_offset( + context, + AddressSpace::Heap, + context.field_type(), + offset, + "memory_store_pointer", + ); + context.build_store(pointer, value)?; + Ok(()) +} + +/// +/// Translates the `mstore8` instruction. +/// +/// Uses the main heap. +/// +pub fn store_byte<'ctx, D>( + context: &mut Context<'ctx, D>, + offset: inkwell::values::IntValue<'ctx>, + value: inkwell::values::IntValue<'ctx>, +) -> anyhow::Result<()> +where + D: Dependency + Clone, +{ + let offset_pointer = Pointer::new_with_offset( + context, + AddressSpace::Heap, + context.byte_type(), + offset, + "mstore8_offset_pointer", + ); + context.build_call( + context.llvm_runtime().mstore8, + &[ + offset_pointer.value.as_basic_value_enum(), + value.as_basic_value_enum(), + ], + "mstore8_call", + ); + Ok(()) +} diff --git a/crates/llvm-context/src/eravm/evm/mod.rs b/crates/llvm-context/src/eravm/evm/mod.rs new file mode 100644 index 0000000..23a5d2f --- /dev/null +++ b/crates/llvm-context/src/eravm/evm/mod.rs @@ -0,0 +1,21 @@ +//! +//! The EVM instructions translation utils. +//! + +pub mod arithmetic; +pub mod bitwise; +pub mod call; +pub mod calldata; +pub mod comparison; +pub mod context; +pub mod create; +pub mod crypto; +pub mod ether_gas; +pub mod event; +pub mod ext_code; +pub mod immutable; +pub mod math; +pub mod memory; +pub mod r#return; +pub mod return_data; +pub mod storage; diff --git a/crates/llvm-context/src/eravm/evm/return.rs b/crates/llvm-context/src/eravm/evm/return.rs new file mode 100644 index 0000000..3b97d24 --- /dev/null +++ b/crates/llvm-context/src/eravm/evm/return.rs @@ -0,0 +1,129 @@ +//! +//! Translates the transaction return operations. +//! + +use crate::eravm::context::address_space::AddressSpace; +use crate::eravm::context::code_type::CodeType; +use crate::eravm::context::pointer::Pointer; +use crate::eravm::context::Context; +use crate::eravm::Dependency; + +/// +/// Translates the `return` instruction. +/// +/// Unlike in EVM, zkSync constructors return the array of contract immutables. +/// +pub fn r#return<'ctx, D>( + context: &mut Context<'ctx, D>, + offset: inkwell::values::IntValue<'ctx>, + length: inkwell::values::IntValue<'ctx>, +) -> anyhow::Result<()> +where + D: Dependency + Clone, +{ + match context.code_type() { + None => { + anyhow::bail!("Return is not available if the contract part is undefined"); + } + Some(CodeType::Deploy) => { + let immutables_offset_pointer = Pointer::new_with_offset( + context, + AddressSpace::HeapAuxiliary, + context.field_type(), + context.field_const(crate::eravm::HEAP_AUX_OFFSET_CONSTRUCTOR_RETURN_DATA), + "immutables_offset_pointer", + ); + context.build_store( + immutables_offset_pointer, + context.field_const(era_compiler_common::BYTE_LENGTH_FIELD as u64), + )?; + + let immutables_number_pointer = Pointer::new_with_offset( + context, + AddressSpace::HeapAuxiliary, + context.field_type(), + context.field_const( + crate::eravm::HEAP_AUX_OFFSET_CONSTRUCTOR_RETURN_DATA + + (era_compiler_common::BYTE_LENGTH_FIELD as u64), + ), + "immutables_number_pointer", + ); + let immutable_values_size = context.immutables_size()?; + context.build_store( + immutables_number_pointer, + context.field_const( + (immutable_values_size / era_compiler_common::BYTE_LENGTH_FIELD) as u64, + ), + )?; + let immutables_size = context.builder().build_int_mul( + context.field_const(immutable_values_size as u64), + context.field_const(2), + "immutables_size", + )?; + let return_data_length = context.builder().build_int_add( + immutables_size, + context.field_const((era_compiler_common::BYTE_LENGTH_FIELD * 2) as u64), + "return_data_length", + )?; + + context.build_exit( + context.integer_const(32, 0), + context.field_const(crate::eravm::HEAP_AUX_OFFSET_CONSTRUCTOR_RETURN_DATA), + return_data_length, + )?; + } + Some(CodeType::Runtime) => { + context.build_exit(context.integer_const(32, 0), offset, length)?; + } + } + + Ok(()) +} + +/// +/// Translates the `revert` instruction. +/// +pub fn revert<'ctx, D>( + context: &mut Context<'ctx, D>, + offset: inkwell::values::IntValue<'ctx>, + length: inkwell::values::IntValue<'ctx>, +) -> anyhow::Result<()> +where + D: Dependency + Clone, +{ + context.build_exit(context.integer_const(32, 1), offset, length) +} + +/// +/// Translates the `stop` instruction. +/// +/// Is the same as `return(0, 0)`. +/// +pub fn stop(context: &mut Context) -> anyhow::Result<()> +where + D: Dependency + Clone, +{ + r#return( + context, + context.integer_const(32, 0), + context.integer_const(32, 0), + ) +} + +/// +/// Translates the `invalid` instruction. +/// +/// Burns all gas using an out-of-bounds memory store, causing a panic. +/// +pub fn invalid(context: &mut Context) -> anyhow::Result<()> +where + D: Dependency + Clone, +{ + crate::eravm::evm::memory::store( + context, + context.field_type().const_all_ones(), + context.field_const(0), + )?; + context.build_call(context.intrinsics().trap, &[], "invalid_trap"); + Ok(()) +} diff --git a/crates/llvm-context/src/eravm/evm/return_data.rs b/crates/llvm-context/src/eravm/evm/return_data.rs new file mode 100644 index 0000000..4655d7d --- /dev/null +++ b/crates/llvm-context/src/eravm/evm/return_data.rs @@ -0,0 +1,93 @@ +//! +//! Translates the return data instructions. +//! + +use inkwell::types::BasicType; +use inkwell::values::BasicValue; + +use crate::eravm::context::address_space::AddressSpace; +use crate::eravm::context::pointer::Pointer; +use crate::eravm::context::Context; +use crate::eravm::Dependency; + +/// +/// Translates the return data size. +/// +pub fn size<'ctx, D>( + context: &mut Context<'ctx, D>, +) -> anyhow::Result> +where + D: Dependency + Clone, +{ + match context.get_global_value(crate::eravm::GLOBAL_RETURN_DATA_SIZE) { + Ok(global) => Ok(global), + Err(_error) => Ok(context.field_const(0).as_basic_value_enum()), + } +} + +/// +/// Translates the return data copy. +/// +pub fn copy<'ctx, D>( + context: &mut Context<'ctx, D>, + destination_offset: inkwell::values::IntValue<'ctx>, + source_offset: inkwell::values::IntValue<'ctx>, + size: inkwell::values::IntValue<'ctx>, +) -> anyhow::Result<()> +where + D: Dependency + Clone, +{ + let error_block = context.append_basic_block("return_data_copy_error_block"); + let join_block = context.append_basic_block("return_data_copy_join_block"); + + let return_data_size = self::size(context)?.into_int_value(); + let copy_slice_end = + context + .builder() + .build_int_add(source_offset, size, "return_data_copy_slice_end")?; + let is_copy_out_of_bounds = context.builder().build_int_compare( + inkwell::IntPredicate::UGT, + copy_slice_end, + return_data_size, + "return_data_copy_is_out_of_bounds", + )?; + context.build_conditional_branch(is_copy_out_of_bounds, error_block, join_block)?; + + context.set_basic_block(error_block); + crate::eravm::evm::r#return::revert(context, context.field_const(0), context.field_const(0))?; + + context.set_basic_block(join_block); + let destination = Pointer::new_with_offset( + context, + AddressSpace::Heap, + context.byte_type(), + destination_offset, + "return_data_copy_destination_pointer", + ); + + let return_data_pointer_global = + context.get_global(crate::eravm::GLOBAL_RETURN_DATA_POINTER)?; + let return_data_pointer_pointer = return_data_pointer_global.into(); + let return_data_pointer = + context.build_load(return_data_pointer_pointer, "return_data_pointer")?; + let source = context.build_gep( + Pointer::new( + context.byte_type(), + return_data_pointer_pointer.address_space, + return_data_pointer.into_pointer_value(), + ), + &[source_offset], + context.byte_type().as_basic_type_enum(), + "return_data_source_pointer", + ); + + context.build_memcpy( + context.intrinsics().memory_copy_from_generic, + destination, + source, + size, + "return_data_copy_memcpy_from_return_data", + )?; + + Ok(()) +} diff --git a/crates/llvm-context/src/eravm/evm/storage.rs b/crates/llvm-context/src/eravm/evm/storage.rs new file mode 100644 index 0000000..9cb1762 --- /dev/null +++ b/crates/llvm-context/src/eravm/evm/storage.rs @@ -0,0 +1,92 @@ +//! +//! Translates the storage operations. +//! + +use crate::eravm::context::address_space::AddressSpace; +use crate::eravm::context::pointer::Pointer; +use crate::eravm::context::Context; +use crate::eravm::Dependency; + +/// +/// Translates the storage load. +/// +pub fn load<'ctx, D>( + context: &mut Context<'ctx, D>, + position: inkwell::values::IntValue<'ctx>, +) -> anyhow::Result> +where + D: Dependency + Clone, +{ + let position_pointer = Pointer::new_with_offset( + context, + AddressSpace::Storage, + context.field_type(), + position, + "storage_load_position_pointer", + ); + context.build_load(position_pointer, "storage_load_value") +} + +/// +/// Translates the storage store. +/// +pub fn store<'ctx, D>( + context: &mut Context<'ctx, D>, + position: inkwell::values::IntValue<'ctx>, + value: inkwell::values::IntValue<'ctx>, +) -> anyhow::Result<()> +where + D: Dependency + Clone, +{ + let position_pointer = Pointer::new_with_offset( + context, + AddressSpace::Storage, + context.field_type(), + position, + "storage_store_position_pointer", + ); + context.build_store(position_pointer, value)?; + Ok(()) +} + +/// +/// Translates the transient storage load. +/// +pub fn transient_load<'ctx, D>( + context: &mut Context<'ctx, D>, + position: inkwell::values::IntValue<'ctx>, +) -> anyhow::Result> +where + D: Dependency + Clone, +{ + let position_pointer = Pointer::new_with_offset( + context, + AddressSpace::TransientStorage, + context.field_type(), + position, + "transient_storage_load_position_pointer", + ); + context.build_load(position_pointer, "transient_storage_load_value") +} + +/// +/// Translates the transient storage store. +/// +pub fn transient_store<'ctx, D>( + context: &mut Context<'ctx, D>, + position: inkwell::values::IntValue<'ctx>, + value: inkwell::values::IntValue<'ctx>, +) -> anyhow::Result<()> +where + D: Dependency + Clone, +{ + let position_pointer = Pointer::new_with_offset( + context, + AddressSpace::TransientStorage, + context.field_type(), + position, + "transient_storage_store_position_pointer", + ); + context.build_store(position_pointer, value)?; + Ok(()) +} diff --git a/crates/llvm-context/src/eravm/extensions/abi.rs b/crates/llvm-context/src/eravm/extensions/abi.rs new file mode 100644 index 0000000..1771541 --- /dev/null +++ b/crates/llvm-context/src/eravm/extensions/abi.rs @@ -0,0 +1,225 @@ +//! +//! Translates the ABI instructions of the EraVM Yul extension. +//! + +use inkwell::types::BasicType; +use inkwell::values::BasicValue; + +use crate::eravm::context::address_space::AddressSpace; +use crate::eravm::context::pointer::Pointer; +use crate::eravm::context::Context; +use crate::eravm::Dependency; + +/// +/// Generates an extra ABI data getter call. +/// +pub fn get_extra_abi_data<'ctx, D>( + context: &mut Context<'ctx, D>, + index: inkwell::values::IntValue<'ctx>, +) -> anyhow::Result> +where + D: Dependency + Clone, +{ + let extra_active_data_global = context.get_global(crate::eravm::GLOBAL_EXTRA_ABI_DATA)?; + let extra_active_data_pointer = extra_active_data_global.into(); + let extra_active_data_element_pointer = context.build_gep( + extra_active_data_pointer, + &[context.field_const(0), index], + context.field_type().as_basic_type_enum(), + "extra_active_data_element_pointer", + ); + context.build_load( + extra_active_data_element_pointer, + "extra_active_data_element_value", + ) +} + +/// +/// Loads the calldata pointer to the active pointer. +/// +pub fn calldata_ptr_to_active<'ctx, D>( + context: &mut Context<'ctx, D>, +) -> anyhow::Result> +where + D: Dependency + Clone, +{ + let calldata_pointer = context.get_global_value(crate::eravm::GLOBAL_CALLDATA_POINTER)?; + context.set_global( + crate::eravm::GLOBAL_ACTIVE_POINTER, + context.byte_type().ptr_type(AddressSpace::Generic.into()), + AddressSpace::Stack, + calldata_pointer, + ); + Ok(context.field_const(1).as_basic_value_enum()) +} + +/// +/// Loads the return data pointer to the active pointer. +/// +pub fn return_data_ptr_to_active<'ctx, D>( + context: &mut Context<'ctx, D>, +) -> anyhow::Result> +where + D: Dependency + Clone, +{ + let calldata_pointer = context.get_global_value(crate::eravm::GLOBAL_RETURN_DATA_POINTER)?; + context.set_global( + crate::eravm::GLOBAL_ACTIVE_POINTER, + context.byte_type().ptr_type(AddressSpace::Generic.into()), + AddressSpace::Stack, + calldata_pointer, + ); + Ok(context.field_const(1).as_basic_value_enum()) +} + +/// +/// Shifts the active pointer by the specified `offset`. +/// +pub fn active_ptr_add_assign<'ctx, D>( + context: &mut Context<'ctx, D>, + offset: inkwell::values::IntValue<'ctx>, +) -> anyhow::Result> +where + D: Dependency + Clone, +{ + let active_pointer = context.get_global_value(crate::eravm::GLOBAL_ACTIVE_POINTER)?; + let active_pointer_shifted = context.build_gep( + Pointer::new( + context.byte_type(), + AddressSpace::Generic, + active_pointer.into_pointer_value(), + ), + &[offset], + context.byte_type().as_basic_type_enum(), + "active_pointer_shifted", + ); + context.set_global( + crate::eravm::GLOBAL_ACTIVE_POINTER, + context.byte_type().ptr_type(AddressSpace::Generic.into()), + AddressSpace::Stack, + active_pointer_shifted.value, + ); + Ok(context.field_const(1).as_basic_value_enum()) +} + +/// +/// Shrinks the active pointer by the specified `offset`. +/// +pub fn active_ptr_shrink_assign<'ctx, D>( + _context: &mut Context<'ctx, D>, + _offset: inkwell::values::IntValue<'ctx>, +) -> anyhow::Result> +where + D: Dependency + Clone, +{ + unimplemented!() +} + +/// +/// Writes the specified `data` into the upper 128 bits of the active pointer. +/// +pub fn active_ptr_pack_assign<'ctx, D>( + _context: &mut Context<'ctx, D>, + _data: inkwell::values::IntValue<'ctx>, +) -> anyhow::Result> +where + D: Dependency + Clone, +{ + unimplemented!() +} + +/// +/// Loads a single word from the active pointer to the stack. +/// +pub fn active_ptr_data_load<'ctx, D>( + context: &mut Context<'ctx, D>, + offset: inkwell::values::IntValue<'ctx>, +) -> anyhow::Result> +where + D: Dependency + Clone, +{ + let active_pointer = context.get_global_value(crate::eravm::GLOBAL_ACTIVE_POINTER)?; + let active_pointer = context.build_gep( + Pointer::new( + context.byte_type(), + AddressSpace::Generic, + active_pointer.into_pointer_value(), + ), + &[offset], + context.field_type().as_basic_type_enum(), + "active_pointer_with_offset", + ); + context.build_load(active_pointer, "active_pointer_value") +} + +/// +/// Returns the active pointer data size. +/// +pub fn active_ptr_data_size<'ctx, D>( + context: &mut Context<'ctx, D>, +) -> anyhow::Result> +where + D: Dependency + Clone, +{ + let active_pointer = context.get_global_value(crate::eravm::GLOBAL_ACTIVE_POINTER)?; + let active_pointer_value = context.builder().build_ptr_to_int( + active_pointer.into_pointer_value(), + context.field_type(), + "active_pointer_value", + )?; + let active_pointer_value_shifted = context.builder().build_right_shift( + active_pointer_value, + context.field_const((era_compiler_common::BIT_LENGTH_X32 * 3) as u64), + false, + "active_pointer_value_shifted", + )?; + let active_pointer_length = context.builder().build_and( + active_pointer_value_shifted, + context.field_const(u32::MAX as u64), + "active_pointer_length", + )?; + Ok(active_pointer_length.as_basic_value_enum()) +} + +/// +/// Copies a chunk of data from the active pointer to the heap. +/// +pub fn active_ptr_data_copy<'ctx, D>( + context: &mut Context<'ctx, D>, + destination_offset: inkwell::values::IntValue<'ctx>, + source_offset: inkwell::values::IntValue<'ctx>, + size: inkwell::values::IntValue<'ctx>, +) -> anyhow::Result> +where + D: Dependency + Clone, +{ + let destination = Pointer::new_with_offset( + context, + AddressSpace::Heap, + context.byte_type(), + destination_offset, + "active_pointer_data_copy_destination_pointer", + ); + + let active_pointer = context.get_global_value(crate::eravm::GLOBAL_ACTIVE_POINTER)?; + let source = context.build_gep( + Pointer::new( + context.byte_type(), + AddressSpace::Generic, + active_pointer.into_pointer_value(), + ), + &[source_offset], + context.byte_type().as_basic_type_enum(), + "active_pointer_data_copy_source_pointer", + ); + + context.build_memcpy( + context.intrinsics().memory_copy_from_generic, + destination, + source, + size, + "active_pointer_data_copy_memcpy_from_child", + )?; + + Ok(context.field_const(1).as_basic_value_enum()) +} diff --git a/crates/llvm-context/src/eravm/extensions/call.rs b/crates/llvm-context/src/eravm/extensions/call.rs new file mode 100644 index 0000000..8a9a64b --- /dev/null +++ b/crates/llvm-context/src/eravm/extensions/call.rs @@ -0,0 +1,302 @@ +//! +//! Translates the call instructions of the EraVM Yul extension. +//! + +use crate::eravm::context::address_space::AddressSpace; +use crate::eravm::context::function::declaration::Declaration as FunctionDeclaration; +use crate::eravm::context::pointer::Pointer; +use crate::eravm::context::Context; +use crate::eravm::Dependency; + +/// +/// Generates a mimic call. +/// +/// The mimic call is a special type of call that can only be used in the system contracts of +/// zkSync. The call allows to call a contract with custom `msg.sender`, allowing to insert +/// system contracts as middlewares. +/// +pub fn mimic<'ctx, D>( + context: &mut Context<'ctx, D>, + function: FunctionDeclaration<'ctx>, + address: inkwell::values::IntValue<'ctx>, + mimic: inkwell::values::IntValue<'ctx>, + abi_data: inkwell::values::BasicValueEnum<'ctx>, + extra_abi_data: Vec>, +) -> anyhow::Result> +where + D: Dependency + Clone, +{ + let status_code_result_pointer = context.build_alloca( + context.field_type(), + "mimic_call_result_status_code_pointer", + ); + context.build_store(status_code_result_pointer, context.field_const(0))?; + + let far_call_result = context + .build_call( + function, + crate::eravm::utils::external_call_arguments( + context, + abi_data, + address, + extra_abi_data, + Some(mimic), + ) + .as_slice(), + "mimic_call_external", + ) + .expect("IntrinsicFunction always returns a flag"); + + let result_abi_data = context + .builder() + .build_extract_value( + far_call_result.into_struct_value(), + 0, + "mimic_call_external_result_abi_data", + ) + .expect("Always exists"); + let result_abi_data_pointer = Pointer::new( + context.byte_type(), + AddressSpace::Generic, + result_abi_data.into_pointer_value(), + ); + + let result_status_code_boolean = context + .builder() + .build_extract_value( + far_call_result.into_struct_value(), + 1, + "mimic_call_external_result_status_code_boolean", + ) + .expect("Always exists"); + let result_status_code = context.builder().build_int_z_extend_or_bit_cast( + result_status_code_boolean.into_int_value(), + context.field_type(), + "mimic_call_external_result_status_code", + )?; + context.build_store(status_code_result_pointer, result_status_code)?; + + context.write_abi_pointer( + result_abi_data_pointer, + crate::eravm::GLOBAL_RETURN_DATA_POINTER, + ); + context.write_abi_data_size( + result_abi_data_pointer, + crate::eravm::GLOBAL_RETURN_DATA_SIZE, + ); + + context.build_load(status_code_result_pointer, "mimic_call_status_code") +} + +/// +/// Generates a raw far call. +/// +/// Such calls can accept extra ABI arguments passed via the virtual machine registers. +/// +pub fn raw_far<'ctx, D>( + _context: &mut Context<'ctx, D>, + _function: FunctionDeclaration<'ctx>, + _address: inkwell::values::IntValue<'ctx>, + _abi_data: inkwell::values::BasicValueEnum<'ctx>, + _output_offset: inkwell::values::IntValue<'ctx>, + _output_length: inkwell::values::IntValue<'ctx>, +) -> anyhow::Result> +where + D: Dependency + Clone, +{ + todo!(); + /* + let status_code_result_pointer = context.build_alloca( + context.field_type(), + "system_far_call_result_status_code_pointer", + ); + context.build_store(status_code_result_pointer, context.field_const(0)); + + let far_call_result = context + .build_call( + function, + crate::eravm::utils::external_call_arguments(context, abi_data, address, vec![], None) + .as_slice(), + "system_far_call_external", + ) + .expect("IntrinsicFunction always returns a flag"); + + let result_abi_data = context + .builder() + .build_extract_value( + far_call_result.into_struct_value(), + 0, + "system_far_call_external_result_abi_data", + ) + .expect("Always exists"); + let result_abi_data_pointer = Pointer::new( + context.byte_type(), + AddressSpace::Generic, + result_abi_data.into_pointer_value(), + ); + + let result_status_code_boolean = context + .builder() + .build_extract_value( + far_call_result.into_struct_value(), + 1, + "system_far_call_external_result_status_code_boolean", + ) + .expect("Always exists"); + let result_status_code = context.builder().build_int_z_extend_or_bit_cast( + result_status_code_boolean.into_int_value(), + context.field_type(), + "system_far_call_external_result_status_code", + ); + context.build_store(status_code_result_pointer, result_status_code); + + let source = result_abi_data_pointer; + + let destination = Pointer::new_with_offset( + context, + AddressSpace::Heap, + context.byte_type(), + output_offset, + "system_far_call_destination", + ); + + context.build_memcpy_return_data( + context.intrinsics().memory_copy_from_generic, + destination, + source, + output_length, + "system_far_call_memcpy_from_child", + ); + + context.write_abi_pointer( + result_abi_data_pointer, + crate::eravm::GLOBAL_RETURN_DATA_POINTER, + ); + context.write_abi_data_size( + result_abi_data_pointer, + crate::eravm::GLOBAL_RETURN_DATA_SIZE, + ); + + let status_code_result = + context.build_load(status_code_result_pointer, "system_call_status_code"); + Ok(status_code_result) + */ +} + +/// +/// Generates a system call. +/// +/// Such calls can accept extra ABI arguments passed via the virtual machine registers. It is used, +/// for example, to pass the callee address and the Ether value to the `msg.value` simulator. +/// +pub fn system<'ctx, D>( + context: &mut Context<'ctx, D>, + function: FunctionDeclaration<'ctx>, + address: inkwell::values::IntValue<'ctx>, + abi_data: inkwell::values::BasicValueEnum<'ctx>, + output_offset: inkwell::values::IntValue<'ctx>, + output_length: inkwell::values::IntValue<'ctx>, + extra_abi_data: Vec>, +) -> anyhow::Result> +where + D: Dependency + Clone, +{ + let status_code_result_pointer = context.build_alloca( + context.field_type(), + "system_far_call_result_status_code_pointer", + ); + context.build_store(status_code_result_pointer, context.field_const(0))?; + + let far_call_result = context + .build_call( + function, + crate::eravm::utils::external_call_arguments( + context, + abi_data, + address, + extra_abi_data, + None, + ) + .as_slice(), + "system_far_call_external", + ) + .expect("IntrinsicFunction always returns a flag"); + + let result_abi_data = context + .builder() + .build_extract_value( + far_call_result.into_struct_value(), + 0, + "system_far_call_external_result_abi_data", + ) + .expect("Always exists"); + let result_abi_data_pointer = Pointer::new( + context.byte_type(), + AddressSpace::Generic, + result_abi_data.into_pointer_value(), + ); + + let result_status_code_boolean = context + .builder() + .build_extract_value( + far_call_result.into_struct_value(), + 1, + "system_far_call_external_result_status_code_boolean", + ) + .expect("Always exists"); + let result_status_code = context.builder().build_int_z_extend_or_bit_cast( + result_status_code_boolean.into_int_value(), + context.field_type(), + "system_far_call_external_result_status_code", + )?; + context.build_store(status_code_result_pointer, result_status_code)?; + + let source = result_abi_data_pointer; + + let destination = Pointer::new_with_offset( + context, + AddressSpace::Heap, + context.byte_type(), + output_offset, + "system_far_call_destination", + ); + + context.build_memcpy_return_data( + context.intrinsics().memory_copy_from_generic, + destination, + source, + output_length, + "system_far_call_memcpy_from_child", + )?; + + context.write_abi_pointer( + result_abi_data_pointer, + crate::eravm::GLOBAL_RETURN_DATA_POINTER, + ); + context.write_abi_data_size( + result_abi_data_pointer, + crate::eravm::GLOBAL_RETURN_DATA_SIZE, + ); + + context.build_load(status_code_result_pointer, "system_call_status_code") +} + +/// +/// Checks if the instruction was called with a correct call type. +/// +pub fn validate_call_type<'ctx>( + expected: FunctionDeclaration<'ctx>, + found: FunctionDeclaration<'ctx>, + instruction_name: &'static str, +) -> anyhow::Result<()> { + if expected != found { + anyhow::bail!( + "Only `{}` is allowed for the `{}` simulation, found `{}`", + expected.value.get_name().to_string_lossy(), + instruction_name, + found.value.get_name().to_string_lossy() + ); + } + + Ok(()) +} diff --git a/crates/llvm-context/src/eravm/extensions/const_array.rs b/crates/llvm-context/src/eravm/extensions/const_array.rs new file mode 100644 index 0000000..26be535 --- /dev/null +++ b/crates/llvm-context/src/eravm/extensions/const_array.rs @@ -0,0 +1,107 @@ +//! +//! Translates the const array instructions of the EraVM Yul extension. +//! + +use inkwell::types::BasicType; +use inkwell::values::BasicValue; + +use crate::eravm::context::address_space::AddressSpace; +use crate::eravm::context::Context; +use crate::eravm::Dependency; + +/// +/// Declares a constant array in the code section. +/// +pub fn _declare<'ctx, D>( + context: &mut Context<'ctx, D>, + index: u8, + size: u16, +) -> anyhow::Result> +where + D: Dependency + Clone, +{ + context.yul_mut().const_array_declare(index, size)?; + + Ok(context.field_const(1).as_basic_value_enum()) +} + +/// +/// Sets a value in a constant array in the code section. +/// +pub fn _set<'ctx, D>( + context: &mut Context<'ctx, D>, + index: u8, + offset: u16, + value: num::BigUint, +) -> anyhow::Result> +where + D: Dependency + Clone, +{ + context.yul_mut().const_array_set(index, offset, value)?; + + Ok(context.field_const(1).as_basic_value_enum()) +} + +/// +/// Finalizes a constant array in the code section, by extracting it from +/// the temporary compile-time storage, and initializing it in LLVM IR. +/// +pub fn _finalize<'ctx, D>( + context: &mut Context<'ctx, D>, + index: u8, +) -> anyhow::Result> +where + D: Dependency + Clone, +{ + let const_array = context.yul_mut().const_array_take(index)?; + let array_type = context.field_type().array_type(const_array.len() as u32); + let array_value = context.field_type().const_array( + const_array + .into_iter() + .map(|value| context.field_const_str_dec(value.to_string().as_str())) + .collect::>>() + .as_slice(), + ); + + context.set_global( + format!( + "{}{:03}", + crate::eravm::r#const::GLOBAL_CONST_ARRAY_PREFIX, + index + ) + .as_str(), + array_type, + AddressSpace::Code, + array_value, + ); + + Ok(context.field_const(1).as_basic_value_enum()) +} + +/// +/// Gets a value from a constant array in the code section. +/// +pub fn _get<'ctx, D>( + context: &mut Context<'ctx, D>, + index: u8, + offset: inkwell::values::IntValue<'ctx>, +) -> anyhow::Result> +where + D: Dependency + Clone, +{ + let identifier = format!( + "{}{:03}", + crate::eravm::r#const::GLOBAL_CONST_ARRAY_PREFIX, + index + ); + let global = context.get_global(identifier.as_str())?; + let pointer = global.into(); + + let pointer = context.build_gep( + pointer, + &[context.field_const(0), offset], + context.field_type().as_basic_type_enum(), + format!("{}_pointer", identifier).as_str(), + ); + context.build_load(pointer, format!("{}_value", identifier).as_str()) +} diff --git a/crates/llvm-context/src/eravm/extensions/general.rs b/crates/llvm-context/src/eravm/extensions/general.rs new file mode 100644 index 0000000..5acc108 --- /dev/null +++ b/crates/llvm-context/src/eravm/extensions/general.rs @@ -0,0 +1,112 @@ +//! +//! Translates the general instructions of the EraVM Yul extension. +//! + +use crate::eravm::context::Context; +use crate::eravm::Dependency; + +/// +/// Generates a call to L1. +/// +pub fn to_l1<'ctx, D>( + _context: &mut Context<'ctx, D>, + _is_first: inkwell::values::IntValue<'ctx>, + _in_0: inkwell::values::IntValue<'ctx>, + _in_1: inkwell::values::IntValue<'ctx>, +) -> anyhow::Result> +where + D: Dependency + Clone, +{ + unimplemented!() +} + +/// +/// Generates a `code source` call. +/// +pub fn code_source<'ctx, D>( + _context: &mut Context<'ctx, D>, +) -> anyhow::Result> +where + D: Dependency + Clone, +{ + todo!() +} + +/// +/// Generates a precompile call. +/// +pub fn precompile<'ctx, D>( + _context: &mut Context<'ctx, D>, + _in_0: inkwell::values::IntValue<'ctx>, + _gas_left: inkwell::values::IntValue<'ctx>, +) -> anyhow::Result> +where + D: Dependency + Clone, +{ + todo!() +} + +/// +/// Generates a `meta` call. +/// +pub fn meta<'ctx, D>( + _context: &mut Context<'ctx, D>, +) -> anyhow::Result> +where + D: Dependency + Clone, +{ + unimplemented!() +} + +/// +/// Generates a `u128` context value setter call. +/// +pub fn set_context_value<'ctx, D>( + _context: &mut Context<'ctx, D>, + _value: inkwell::values::IntValue<'ctx>, +) -> anyhow::Result> +where + D: Dependency + Clone, +{ + unimplemented!() +} + +/// +/// Generates a public data price setter call. +/// +pub fn set_pubdata_price<'ctx, D>( + _context: &mut Context<'ctx, D>, + _value: inkwell::values::IntValue<'ctx>, +) -> anyhow::Result> +where + D: Dependency + Clone, +{ + unimplemented!() +} + +/// +/// Generates a transaction counter increment call. +/// +pub fn increment_tx_counter<'ctx, D>( + _context: &mut Context<'ctx, D>, +) -> anyhow::Result> +where + D: Dependency + Clone, +{ + unimplemented!() +} + +/// +/// Generates an event call. +/// +pub fn event<'ctx, D>( + _context: &mut Context<'ctx, D>, + _operand_1: inkwell::values::IntValue<'ctx>, + _operand_2: inkwell::values::IntValue<'ctx>, + _is_initializer: bool, +) -> anyhow::Result> +where + D: Dependency + Clone, +{ + todo!() +} diff --git a/crates/llvm-context/src/eravm/extensions/math.rs b/crates/llvm-context/src/eravm/extensions/math.rs new file mode 100644 index 0000000..68069e5 --- /dev/null +++ b/crates/llvm-context/src/eravm/extensions/math.rs @@ -0,0 +1,52 @@ +//! +//! Translates the math instructions of the EraVM Yul extension. +//! + +use inkwell::values::BasicValue; + +use crate::eravm::context::Context; +use crate::eravm::Dependency; + +/// +/// Performs a multiplication, returning the higher register, that is the overflown part. +/// +pub fn multiplication_512<'ctx, D>( + context: &mut Context<'ctx, D>, + operand_1: inkwell::values::IntValue<'ctx>, + operand_2: inkwell::values::IntValue<'ctx>, +) -> anyhow::Result> +where + D: Dependency + Clone, +{ + let operand_1_extended = context.builder().build_int_z_extend_or_bit_cast( + operand_1, + context.integer_type(era_compiler_common::BIT_LENGTH_FIELD * 2), + "multiplication_512_operand_1_extended", + )?; + let operand_2_extended = context.builder().build_int_z_extend_or_bit_cast( + operand_2, + context.integer_type(era_compiler_common::BIT_LENGTH_FIELD * 2), + "multiplication_512_operand_2_extended", + )?; + let result_extended = context.builder().build_int_mul( + operand_1_extended, + operand_2_extended, + "multiplication_512_result_extended", + )?; + let result_shifted = context.builder().build_right_shift( + result_extended, + context.integer_const( + era_compiler_common::BIT_LENGTH_FIELD * 2, + era_compiler_common::BIT_LENGTH_FIELD as u64, + ), + false, + "multiplication_512_result_shifted", + )?; + let result = context.builder().build_int_truncate_or_bit_cast( + result_shifted, + context.field_type(), + "multiplication_512_result", + )?; + + Ok(result.as_basic_value_enum()) +} diff --git a/crates/llvm-context/src/eravm/extensions/mod.rs b/crates/llvm-context/src/eravm/extensions/mod.rs new file mode 100644 index 0000000..9ef916e --- /dev/null +++ b/crates/llvm-context/src/eravm/extensions/mod.rs @@ -0,0 +1,9 @@ +//! +//! The EraVM instructions translation utils. +//! + +pub mod abi; +pub mod call; +pub mod const_array; +pub mod general; +pub mod math; diff --git a/crates/llvm-context/src/eravm/metadata_hash.rs b/crates/llvm-context/src/eravm/metadata_hash.rs new file mode 100644 index 0000000..c4586bc --- /dev/null +++ b/crates/llvm-context/src/eravm/metadata_hash.rs @@ -0,0 +1,33 @@ +//! +//! The metadata hash mode. +//! + +use std::str::FromStr; + +use serde::Deserialize; +use serde::Serialize; + +/// +/// The metadata hash mode. +/// +#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Hash)] +pub enum MetadataHash { + /// Do not include bytecode hash. + #[serde(rename = "none")] + None, + /// The default keccak256 hash. + #[serde(rename = "keccak256")] + Keccak256, +} + +impl FromStr for MetadataHash { + type Err = anyhow::Error; + + fn from_str(string: &str) -> Result { + match string { + "none" => Ok(Self::None), + "keccak256" => Ok(Self::Keccak256), + _ => anyhow::bail!("Unknown bytecode hash mode: `{}`", string), + } + } +} diff --git a/crates/llvm-context/src/eravm/mod.rs b/crates/llvm-context/src/eravm/mod.rs new file mode 100644 index 0000000..6037d50 --- /dev/null +++ b/crates/llvm-context/src/eravm/mod.rs @@ -0,0 +1,189 @@ +//! +//! The LLVM context library. +//! + +pub mod r#const; +pub mod context; +pub mod evm; +pub mod extensions; +pub mod metadata_hash; +pub mod utils; + +pub use self::r#const::*; + +use crate::debug_config::DebugConfig; +use crate::optimizer::settings::Settings as OptimizerSettings; + +use self::context::build::Build; +use self::context::Context; + +/// +/// Initializes the EraVM target machine. +/// +pub fn initialize_target() { + inkwell::targets::Target::initialize_riscv(&Default::default()); +} + +/// +/// Builds EraVM assembly text. +/// +pub fn build_assembly_text( + contract_path: &str, + assembly_text: &str, + _metadata_hash: Option<[u8; era_compiler_common::BYTE_LENGTH_FIELD]>, + debug_config: Option<&DebugConfig>, +) -> anyhow::Result { + if let Some(debug_config) = debug_config { + debug_config.dump_assembly(contract_path, assembly_text)?; + } + + /* + let mut assembly = + zkevm_assembly::Assembly::from_string(assembly_text.to_owned(), metadata_hash).map_err( + |error| { + anyhow::anyhow!( + "The contract `{}` assembly parsing error: {}", + contract_path, + error, + ) + }, + )?; + + let bytecode_words = match zkevm_assembly::get_encoding_mode() { + zkevm_assembly::RunningVmEncodingMode::Production => { assembly.compile_to_bytecode_for_mode::<8, zkevm_opcode_defs::decoding::EncodingModeProduction>() }, + zkevm_assembly::RunningVmEncodingMode::Testing => { assembly.compile_to_bytecode_for_mode::<16, zkevm_opcode_defs::decoding::EncodingModeTesting>() }, + } + .map_err(|error| { + anyhow::anyhow!( + "The contract `{}` assembly-to-bytecode conversion error: {}", + contract_path, + error, + ) + })?; + + let bytecode_hash = match zkevm_assembly::get_encoding_mode() { + zkevm_assembly::RunningVmEncodingMode::Production => { + zkevm_opcode_defs::utils::bytecode_to_code_hash_for_mode::< + 8, + zkevm_opcode_defs::decoding::EncodingModeProduction, + >(bytecode_words.as_slice()) + } + zkevm_assembly::RunningVmEncodingMode::Testing => { + zkevm_opcode_defs::utils::bytecode_to_code_hash_for_mode::< + 16, + zkevm_opcode_defs::decoding::EncodingModeTesting, + >(bytecode_words.as_slice()) + } + } + .map(hex::encode) + .map_err(|_error| { + anyhow::anyhow!("The contract `{}` bytecode hashing error", contract_path,) + })?; + + let bytecode = bytecode_words.into_iter().flatten().collect(); + */ + + Ok(Build::new( + assembly_text.to_owned(), + Default::default(), + hex::decode(assembly_text).unwrap(), + Default::default(), + )) +} + +/// +/// Implemented by items which are translated into LLVM IR. +/// +#[allow(clippy::upper_case_acronyms)] +pub trait WriteLLVM +where + D: Dependency + Clone, +{ + /// + /// Declares the entity in the LLVM IR. + /// Is usually performed in order to use the item before defining it. + /// + fn declare(&mut self, _context: &mut Context) -> anyhow::Result<()> { + Ok(()) + } + + /// + /// Translates the entity into LLVM IR. + /// + fn into_llvm(self, context: &mut Context) -> anyhow::Result<()>; +} + +/// +/// The dummy LLVM writable entity. +/// +#[derive(Debug, Default, Clone)] +pub struct DummyLLVMWritable {} + +impl WriteLLVM for DummyLLVMWritable +where + D: Dependency + Clone, +{ + fn into_llvm(self, _context: &mut Context) -> anyhow::Result<()> { + Ok(()) + } +} + +/// +/// Implemented by items managing project dependencies. +/// +pub trait Dependency { + /// + /// Compiles a project dependency. + /// + fn compile( + dependency: Self, + path: &str, + optimizer_settings: OptimizerSettings, + is_system_mode: bool, + include_metadata_hash: bool, + debug_config: Option, + ) -> anyhow::Result; + + /// + /// Resolves a full contract path. + /// + fn resolve_path(&self, identifier: &str) -> anyhow::Result; + + /// + /// Resolves a library address. + /// + fn resolve_library(&self, path: &str) -> anyhow::Result; +} + +/// +/// The dummy dependency entity. +/// +#[derive(Debug, Default, Clone)] +pub struct DummyDependency {} + +impl Dependency for DummyDependency { + fn compile( + _dependency: Self, + _path: &str, + _optimizer_settings: OptimizerSettings, + _is_system_mode: bool, + _include_metadata_hash: bool, + _debug_config: Option, + ) -> anyhow::Result { + Ok(String::new()) + } + + /// + /// Resolves a full contract path. + /// + fn resolve_path(&self, _identifier: &str) -> anyhow::Result { + Ok(String::new()) + } + + /// + /// Resolves a library address. + /// + fn resolve_library(&self, _path: &str) -> anyhow::Result { + Ok(String::new()) + } +} diff --git a/crates/llvm-context/src/eravm/utils.rs b/crates/llvm-context/src/eravm/utils.rs new file mode 100644 index 0000000..f423a0a --- /dev/null +++ b/crates/llvm-context/src/eravm/utils.rs @@ -0,0 +1,235 @@ +//! +//! Some LLVM IR generator utilies. +//! + +use inkwell::values::BasicValue; + +use crate::eravm::context::address_space::AddressSpace; +use crate::eravm::context::function::llvm_runtime::LLVMRuntime; +use crate::eravm::context::Context; +use crate::eravm::Dependency; + +/// +/// Clamps `value` to `max_value`, if `value` is bigger than `max_value`. +/// +pub fn clamp<'ctx, D>( + context: &mut Context<'ctx, D>, + value: inkwell::values::IntValue<'ctx>, + max_value: inkwell::values::IntValue<'ctx>, + name: &str, +) -> anyhow::Result> +where + D: Dependency + Clone, +{ + let in_bounds_block = context.append_basic_block(format!("{name}_is_bounds_block").as_str()); + let join_block = context.append_basic_block(format!("{name}_join_block").as_str()); + + let pointer = context.build_alloca(context.field_type(), format!("{name}_pointer").as_str()); + context.build_store(pointer, max_value)?; + + let is_in_bounds = context.builder().build_int_compare( + inkwell::IntPredicate::ULE, + value, + max_value, + format!("{name}_is_in_bounds").as_str(), + )?; + context.build_conditional_branch(is_in_bounds, in_bounds_block, join_block)?; + + context.set_basic_block(in_bounds_block); + context.build_store(pointer, value)?; + context.build_unconditional_branch(join_block); + + context.set_basic_block(join_block); + let result = context.build_load(pointer, name)?; + Ok(result.into_int_value()) +} + +/// +/// Generates an exception. +/// +pub fn throw(context: &Context) +where + D: Dependency + Clone, +{ + context.build_call( + context.llvm_runtime().cxa_throw, + &[context + .byte_type() + .ptr_type(AddressSpace::Stack.into()) + .get_undef() + .as_basic_value_enum(); 3], + LLVMRuntime::FUNCTION_CXA_THROW, + ); + context.build_unreachable(); +} + +/// +/// Returns the full list of arguments for an external call. +/// +/// Performs the extra ABI data padding and adds the mimic call extra argument. +/// +pub fn external_call_arguments<'ctx, D>( + _context: &Context<'ctx, D>, + abi_data: inkwell::values::BasicValueEnum<'ctx>, + address: inkwell::values::IntValue<'ctx>, + _extra_abi_data: Vec>, + mimic: Option>, +) -> Vec> +where + D: Dependency + Clone, +{ + let mut result = Vec::with_capacity( + crate::eravm::context::function::runtime::entry::Entry::MANDATORY_ARGUMENTS_COUNT + + crate::eravm::EXTRA_ABI_DATA_SIZE + + usize::from(mimic.is_some()), + ); + result.push(abi_data); + result.push(address.as_basic_value_enum()); + //result.extend( + // pad_extra_abi_data(context, extra_abi_data) + // .into_iter() + // .map(|value| value.as_basic_value_enum()), + //); + if let Some(mimic) = mimic { + result.push(mimic.as_basic_value_enum()); + } + result +} + +/// +/// Generates an ABI data for an external call. +/// +/// If `gas` is `None`, it is fetched from the contract context. +/// +pub fn abi_data<'ctx, D>( + context: &mut Context<'ctx, D>, + input_offset: inkwell::values::IntValue<'ctx>, + input_length: inkwell::values::IntValue<'ctx>, + gas: Option>, + address_space: AddressSpace, + is_system_call: bool, +) -> anyhow::Result> +where + D: Dependency + Clone, +{ + let input_offset = crate::eravm::utils::clamp( + context, + input_offset, + context.field_const(u32::MAX as u64), + "abi_data_input_offset", + )?; + let input_length = crate::eravm::utils::clamp( + context, + input_length, + context.field_const(u32::MAX as u64), + "abi_data_input_length", + )?; + + let gas = match gas { + Some(gas) => gas, + None => crate::eravm::evm::ether_gas::gas(context)?.into_int_value(), + }; + let gas = crate::eravm::utils::clamp( + context, + gas, + context.field_const(u32::MAX as u64), + "abi_data_gas", + )?; + + let input_offset_shifted = context.builder().build_left_shift( + input_offset, + context.field_const((era_compiler_common::BIT_LENGTH_X32 * 2) as u64), + "abi_data_input_offset_shifted", + )?; + let input_length_shifted = context.builder().build_left_shift( + input_length, + context.field_const((era_compiler_common::BIT_LENGTH_X32 * 3) as u64), + "abi_data_input_length_shifted", + )?; + let gas_shifted = context.builder().build_left_shift( + gas, + context.field_const((era_compiler_common::BIT_LENGTH_X32 * 6) as u64), + "abi_data_gas_shifted", + )?; + + let mut abi_data = context.builder().build_int_add( + input_offset_shifted, + input_length_shifted, + "abi_data_offset_and_length", + )?; + abi_data = context + .builder() + .build_int_add(abi_data, gas_shifted, "abi_data_add_gas")?; + if let AddressSpace::HeapAuxiliary = address_space { + let auxiliary_heap_marker_shifted = context.builder().build_left_shift( + context.field_const(zkevm_opcode_defs::FarCallForwardPageType::UseAuxHeap as u64), + context.field_const((era_compiler_common::BIT_LENGTH_X32 * 7) as u64), + "abi_data_auxiliary_heap_marker_shifted", + )?; + abi_data = context.builder().build_int_add( + abi_data, + auxiliary_heap_marker_shifted, + "abi_data_add_heap_auxiliary_marker", + )?; + } + if is_system_call { + let auxiliary_heap_marker_shifted = context.builder().build_left_shift( + context.field_const(zkevm_opcode_defs::FarCallForwardPageType::UseAuxHeap as u64), + context.field_const( + ((era_compiler_common::BIT_LENGTH_X32 * 7) + + (era_compiler_common::BIT_LENGTH_BYTE * 3)) as u64, + ), + "abi_data_system_call_marker_shifted", + )?; + abi_data = context.builder().build_int_add( + abi_data, + auxiliary_heap_marker_shifted, + "abi_data_add_system_call_marker", + )?; + } + + Ok(abi_data.as_basic_value_enum()) +} + +/// +/// Pads the extra ABI data with `i256::undef`, so it always consists of 10 values. +/// +pub fn pad_extra_abi_data<'ctx, D>( + context: &Context<'ctx, D>, + initial_data: Vec>, +) -> [inkwell::values::IntValue<'ctx>; crate::eravm::EXTRA_ABI_DATA_SIZE] +where + D: Dependency + Clone, +{ + let mut padded_data = initial_data; + padded_data.extend(vec![ + context.field_undef(); + crate::eravm::EXTRA_ABI_DATA_SIZE - padded_data.len() + ]); + padded_data.try_into().expect("Always valid") +} + +/// +/// Computes the `keccak256` hash for `preimage`. +/// +pub fn keccak256(preimage: &[u8]) -> String { + use sha3::Digest; + + let hash_bytes = sha3::Keccak256::digest(preimage); + hash_bytes + .into_iter() + .map(|byte| format!("{byte:02x}")) + .collect::>() + .join("") +} + +#[cfg(test)] +mod tests { + #[test] + fn keccak256() { + assert_eq!( + super::keccak256("zksync".as_bytes()), + "0238fb1ab06c28c32885f9a4842207ac480c2467df26b6c58e201679628c5a5b" + ); + } +} diff --git a/crates/llvm-context/src/lib.rs b/crates/llvm-context/src/lib.rs new file mode 100644 index 0000000..95741bb --- /dev/null +++ b/crates/llvm-context/src/lib.rs @@ -0,0 +1,82 @@ +//! +//! The LLVM context library. +//! + +pub(crate) mod debug_config; +pub(crate) mod eravm; +pub(crate) mod optimizer; +pub(crate) mod target_machine; + +pub use self::debug_config::ir_type::IRType as DebugConfigIR; +pub use self::debug_config::DebugConfig; +pub use self::eravm::build_assembly_text as eravm_build_assembly_text; +pub use self::eravm::context::address_space::AddressSpace as EraVMAddressSpace; +pub use self::eravm::context::argument::Argument as EraVMArgument; +pub use self::eravm::context::attribute::Attribute as EraVMAttribute; +pub use self::eravm::context::build::Build as EraVMBuild; +pub use self::eravm::context::code_type::CodeType as EraVMCodeType; +pub use self::eravm::context::evmla_data::EVMLAData as EraVMContextEVMLAData; +pub use self::eravm::context::function::block::evmla_data::key::Key as EraVMFunctionBlockKey; +pub use self::eravm::context::function::block::evmla_data::EVMLAData as EraVMFunctionBlockEVMLAData; +pub use self::eravm::context::function::block::Block as EraVMFunctionBlock; +pub use self::eravm::context::function::declaration::Declaration as EraVMFunctionDeclaration; +pub use self::eravm::context::function::evmla_data::EVMLAData as EraVMFunctionEVMLAData; +pub use self::eravm::context::function::intrinsics::Intrinsics as EraVMIntrinsicFunction; +pub use self::eravm::context::function::llvm_runtime::LLVMRuntime as EraVMLLVMRuntime; +pub use self::eravm::context::function::r#return::Return as EraVMFunctionReturn; +pub use self::eravm::context::function::runtime::deploy_code::DeployCode as EraVMDeployCodeFunction; +pub use self::eravm::context::function::runtime::entry::Entry as EraVMEntryFunction; +pub use self::eravm::context::function::runtime::runtime_code::RuntimeCode as EraVMRuntimeCodeFunction; +pub use self::eravm::context::function::runtime::Runtime as EraVMRuntime; +pub use self::eravm::context::function::vyper_data::VyperData as EraVMFunctionVyperData; +pub use self::eravm::context::function::yul_data::YulData as EraVMFunctionYulData; +pub use self::eravm::context::function::Function as EraVMFunction; +pub use self::eravm::context::global::Global as EraVMGlobal; +pub use self::eravm::context::pointer::Pointer as EraVMPointer; +pub use self::eravm::context::r#loop::Loop as EraVMLoop; +pub use self::eravm::context::solidity_data::SolidityData as EraVMContextSolidityData; +pub use self::eravm::context::vyper_data::VyperData as EraVMContextVyperData; +pub use self::eravm::context::yul_data::YulData as EraVMContextYulData; +pub use self::eravm::context::Context as EraVMContext; +pub use self::eravm::evm::arithmetic as eravm_evm_arithmetic; +pub use self::eravm::evm::bitwise as eravm_evm_bitwise; +pub use self::eravm::evm::call as eravm_evm_call; +pub use self::eravm::evm::calldata as eravm_evm_calldata; +pub use self::eravm::evm::comparison as eravm_evm_comparison; +pub use self::eravm::evm::context as eravm_evm_contract_context; +pub use self::eravm::evm::create as eravm_evm_create; +pub use self::eravm::evm::crypto as eravm_evm_crypto; +pub use self::eravm::evm::ether_gas as eravm_evm_ether_gas; +pub use self::eravm::evm::event as eravm_evm_event; +pub use self::eravm::evm::ext_code as eravm_evm_ext_code; +pub use self::eravm::evm::immutable as eravm_evm_immutable; +pub use self::eravm::evm::math as eravm_evm_math; +pub use self::eravm::evm::memory as eravm_evm_memory; +pub use self::eravm::evm::r#return as eravm_evm_return; +pub use self::eravm::evm::return_data as eravm_evm_return_data; +pub use self::eravm::evm::storage as eravm_evm_storage; +pub use self::eravm::extensions::abi as eravm_abi; +pub use self::eravm::extensions::call as eravm_call; +pub use self::eravm::extensions::general as eravm_general; +pub use self::eravm::extensions::math as eravm_math; +pub use self::eravm::metadata_hash::MetadataHash as EraVMMetadataHash; +pub use self::eravm::r#const as eravm_const; +pub use self::eravm::utils as eravm_utils; +pub use self::eravm::Dependency as EraVMDependency; +pub use self::eravm::DummyDependency as EraVMDummyDependency; +pub use self::eravm::DummyLLVMWritable as EraVMDummyLLVMWritable; +pub use self::eravm::WriteLLVM as EraVMWriteLLVM; +pub use self::optimizer::settings::size_level::SizeLevel as OptimizerSettingsSizeLevel; +pub use self::optimizer::settings::Settings as OptimizerSettings; +pub use self::optimizer::Optimizer; +pub use self::target_machine::target::Target; +pub use self::target_machine::TargetMachine; + +/// +/// Initializes the target machine. +/// +pub fn initialize_target(target: Target) { + match target { + Target::PVM => self::eravm::initialize_target(), + } +} diff --git a/crates/llvm-context/src/optimizer/mod.rs b/crates/llvm-context/src/optimizer/mod.rs new file mode 100644 index 0000000..fe96ce3 --- /dev/null +++ b/crates/llvm-context/src/optimizer/mod.rs @@ -0,0 +1,51 @@ +//! +//! The LLVM optimizing tools. +//! + +pub mod settings; + +use serde::Deserialize; +use serde::Serialize; + +use crate::target_machine::TargetMachine; + +use self::settings::Settings; + +/// +/// The LLVM optimizing tools. +/// +#[derive(Debug, Serialize, Deserialize)] +pub struct Optimizer { + /// The optimizer settings. + settings: Settings, +} + +impl Optimizer { + /// + /// A shortcut constructor. + /// + pub fn new(settings: Settings) -> Self { + Self { settings } + } + + /// + /// Runs the new pass manager. + /// + pub fn run( + &self, + target_machine: &TargetMachine, + module: &inkwell::module::Module, + ) -> Result<(), inkwell::support::LLVMString> { + target_machine.run_optimization_passes( + module, + format!("default", self.settings.middle_end_as_string()).as_str(), + ) + } + + /// + /// Returns the optimizer settings reference. + /// + pub fn settings(&self) -> &Settings { + &self.settings + } +} diff --git a/crates/llvm-context/src/optimizer/settings/mod.rs b/crates/llvm-context/src/optimizer/settings/mod.rs new file mode 100644 index 0000000..56ffbaf --- /dev/null +++ b/crates/llvm-context/src/optimizer/settings/mod.rs @@ -0,0 +1,273 @@ +//! +//! The LLVM optimizer settings. +//! + +pub mod size_level; + +use serde::Deserialize; +use serde::Serialize; + +use itertools::Itertools; + +use self::size_level::SizeLevel; + +/// +/// The LLVM optimizer settings. +/// +#[derive(Debug, Serialize, Deserialize, Clone, Eq)] +pub struct Settings { + /// The middle-end optimization level. + pub level_middle_end: inkwell::OptimizationLevel, + /// The middle-end size optimization level. + pub level_middle_end_size: SizeLevel, + /// The back-end optimization level. + pub level_back_end: inkwell::OptimizationLevel, + + /// Fallback to optimizing for size if the bytecode is too large. + pub is_fallback_to_size_enabled: bool, + /// Whether the system request memoization is disabled. + pub is_system_request_memoization_disabled: bool, + + /// Whether the LLVM `verify each` option is enabled. + pub is_verify_each_enabled: bool, + /// Whether the LLVM `debug logging` option is enabled. + pub is_debug_logging_enabled: bool, +} + +impl Settings { + /// + /// A shortcut constructor. + /// + pub fn new( + level_middle_end: inkwell::OptimizationLevel, + level_middle_end_size: SizeLevel, + level_back_end: inkwell::OptimizationLevel, + ) -> Self { + Self { + level_middle_end, + level_middle_end_size, + level_back_end, + + is_fallback_to_size_enabled: false, + is_system_request_memoization_disabled: false, + + is_verify_each_enabled: false, + is_debug_logging_enabled: false, + } + } + + /// + /// A shortcut constructor with debugging tools. + /// + pub fn new_debug( + level_middle_end: inkwell::OptimizationLevel, + level_middle_end_size: SizeLevel, + level_back_end: inkwell::OptimizationLevel, + + is_verify_each_enabled: bool, + is_debug_logging_enabled: bool, + ) -> Self { + Self { + level_middle_end, + level_middle_end_size, + level_back_end, + + is_fallback_to_size_enabled: false, + is_system_request_memoization_disabled: false, + + is_verify_each_enabled, + is_debug_logging_enabled, + } + } + + /// + /// Creates settings from a CLI optimization parameter. + /// + pub fn try_from_cli(value: char) -> anyhow::Result { + Ok(match value { + '0' => Self::new( + // The middle-end optimization level. + inkwell::OptimizationLevel::None, + // The middle-end size optimization level. + SizeLevel::Zero, + // The back-end optimization level. + inkwell::OptimizationLevel::None, + ), + '1' => Self::new( + inkwell::OptimizationLevel::Less, + SizeLevel::Zero, + // The back-end does not currently distinguish between O1, O2, and O3. + inkwell::OptimizationLevel::Less, + ), + '2' => Self::new( + inkwell::OptimizationLevel::Default, + SizeLevel::Zero, + // The back-end does not currently distinguish between O1, O2, and O3. + inkwell::OptimizationLevel::Default, + ), + '3' => Self::new( + inkwell::OptimizationLevel::Aggressive, + SizeLevel::Zero, + inkwell::OptimizationLevel::Aggressive, + ), + 's' => Self::new( + // The middle-end optimization level is ignored when SizeLevel is set. + inkwell::OptimizationLevel::Default, + SizeLevel::S, + inkwell::OptimizationLevel::Aggressive, + ), + 'z' => Self::new( + // The middle-end optimization level is ignored when SizeLevel is set. + inkwell::OptimizationLevel::Default, + SizeLevel::Z, + inkwell::OptimizationLevel::Aggressive, + ), + char => anyhow::bail!("Unexpected optimization option '{}'", char), + }) + } + + /// + /// Returns the settings without optimizations. + /// + pub fn none() -> Self { + Self::new( + inkwell::OptimizationLevel::None, + SizeLevel::Zero, + inkwell::OptimizationLevel::None, + ) + } + + /// + /// Returns the settings for the optimal number of VM execution cycles. + /// + pub fn cycles() -> Self { + Self::new( + inkwell::OptimizationLevel::Aggressive, + SizeLevel::Zero, + inkwell::OptimizationLevel::Aggressive, + ) + } + + /// + /// Returns the settings for the optimal size. + /// + pub fn size() -> Self { + Self::new( + inkwell::OptimizationLevel::Default, + SizeLevel::Z, + inkwell::OptimizationLevel::Aggressive, + ) + } + + /// + /// Returns the middle-end optimization parameter as string. + /// + pub fn middle_end_as_string(&self) -> String { + match self.level_middle_end_size { + SizeLevel::Zero => (self.level_middle_end as u8).to_string(), + size_level => size_level.to_string(), + } + } + + /// + /// Checks whether there are middle-end optimizations enabled. + /// + pub fn is_middle_end_enabled(&self) -> bool { + self.level_middle_end != inkwell::OptimizationLevel::None + || self.level_middle_end_size != SizeLevel::Zero + } + + /// + /// Returns all possible combinations of the optimizer settings. + /// + /// Used only for testing purposes. + /// + pub fn combinations() -> Vec { + let performance_combinations: Vec = vec![ + inkwell::OptimizationLevel::None, + inkwell::OptimizationLevel::Less, + inkwell::OptimizationLevel::Default, + inkwell::OptimizationLevel::Aggressive, + ] + .into_iter() + .cartesian_product(vec![ + inkwell::OptimizationLevel::None, + inkwell::OptimizationLevel::Aggressive, + ]) + .map(|(optimization_level_middle, optimization_level_back)| { + Self::new( + optimization_level_middle, + SizeLevel::Zero, + optimization_level_back, + ) + }) + .collect(); + + let size_combinations: Vec = vec![SizeLevel::S, SizeLevel::Z] + .into_iter() + .cartesian_product(vec![ + inkwell::OptimizationLevel::None, + inkwell::OptimizationLevel::Aggressive, + ]) + .map(|(size_level, optimization_level_back)| { + Self::new( + inkwell::OptimizationLevel::Default, + size_level, + optimization_level_back, + ) + }) + .collect(); + + let mut combinations = performance_combinations; + combinations.extend(size_combinations); + + combinations + } + + /// + /// Sets the fallback to optimizing for size if the bytecode is too large. + /// + pub fn enable_fallback_to_size(&mut self) { + self.is_fallback_to_size_enabled = true; + } + + /// + /// Disables the system request memoization. + /// + pub fn disable_system_request_memoization(&mut self) { + self.is_system_request_memoization_disabled = true; + } + + /// + /// Whether the fallback to optimizing for size is enabled. + /// + pub fn is_fallback_to_size_enabled(&self) -> bool { + self.is_fallback_to_size_enabled + } + + /// + /// Whether the system request memoization is disabled. + /// + pub fn is_system_request_memoization_disabled(&self) -> bool { + self.is_system_request_memoization_disabled + } +} + +impl PartialEq for Settings { + fn eq(&self, other: &Self) -> bool { + self.level_middle_end == other.level_middle_end + && self.level_middle_end_size == other.level_middle_end_size + && self.level_back_end == other.level_back_end + } +} + +impl std::fmt::Display for Settings { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "M{}B{}", + self.middle_end_as_string(), + self.level_back_end as u8, + ) + } +} diff --git a/crates/llvm-context/src/optimizer/settings/size_level.rs b/crates/llvm-context/src/optimizer/settings/size_level.rs new file mode 100644 index 0000000..dfc255e --- /dev/null +++ b/crates/llvm-context/src/optimizer/settings/size_level.rs @@ -0,0 +1,39 @@ +//! +//! The LLVM optimizer settings size level. +//! + +use serde::Deserialize; +use serde::Serialize; + +/// +/// The LLVM optimizer settings size level. +/// +#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)] +pub enum SizeLevel { + /// No size optimizations. + Zero, + /// The default size optimizations. + S, + /// The aggresize size optimizations. + Z, +} + +impl From for u32 { + fn from(level: SizeLevel) -> Self { + match level { + SizeLevel::Zero => 0, + SizeLevel::S => 1, + SizeLevel::Z => 2, + } + } +} + +impl std::fmt::Display for SizeLevel { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + SizeLevel::Zero => write!(f, "0"), + SizeLevel::S => write!(f, "s"), + SizeLevel::Z => write!(f, "z"), + } + } +} diff --git a/crates/llvm-context/src/target_machine/mod.rs b/crates/llvm-context/src/target_machine/mod.rs new file mode 100644 index 0000000..a044dfc --- /dev/null +++ b/crates/llvm-context/src/target_machine/mod.rs @@ -0,0 +1,126 @@ +//! +//! The LLVM target machine. +//! + +pub mod target; + +use crate::optimizer::settings::size_level::SizeLevel as OptimizerSettingsSizeLevel; +use crate::optimizer::settings::Settings as OptimizerSettings; + +use self::target::Target; + +/// +/// The LLVM target machine. +/// +#[derive(Debug)] +pub struct TargetMachine { + /// The LLVM target. + target: Target, + /// The LLVM target machine reference. + target_machine: inkwell::targets::TargetMachine, + /// The optimizer settings. + optimizer_settings: OptimizerSettings, +} + +impl TargetMachine { + /// The LLVM target name. + pub const VM_TARGET_NAME: &'static str = "riscv32"; + + /// The LLVM target triple. + pub const VM_TARGET_TRIPLE: &'static str = "riscv32-unknown-unknown-elf"; + + /// + /// A shortcut constructor. + /// + /// A separate instance for every optimization level is created. + /// + pub fn new(target: Target, optimizer_settings: &OptimizerSettings) -> anyhow::Result { + let target_machine = inkwell::targets::Target::from_name(target.name()) + .ok_or_else(|| anyhow::anyhow!("LLVM target machine `{}` not found", target.name()))? + .create_target_machine( + &inkwell::targets::TargetTriple::create(target.triple()), + "generic-rv32", + "+e,+m", + optimizer_settings.level_back_end, + inkwell::targets::RelocMode::PIC, + inkwell::targets::CodeModel::Default, + ) + .ok_or_else(|| { + anyhow::anyhow!( + "LLVM target machine `{}` initialization error", + target.name(), + ) + })?; + + Ok(Self { + target, + target_machine, + optimizer_settings: optimizer_settings.to_owned(), + }) + } + + /// + /// Sets the target-specific data in the module. + /// + pub fn set_target_data(&self, module: &inkwell::module::Module) { + module.set_triple(&self.target_machine.get_triple()); + let data_layout = self + .target_machine + .get_target_data() + .get_data_layout() + .as_str() + .to_str() + .expect("datalayout sting should be valid") + .to_owned(); + + module.set_data_layout(&self.target_machine.get_target_data().get_data_layout()); + } + + /// + /// Writes the LLVM module to a memory buffer. + /// + pub fn write_to_memory_buffer( + &self, + module: &inkwell::module::Module, + ) -> Result { + match self.target { + Target::PVM => self + .target_machine + .write_to_memory_buffer(module, inkwell::targets::FileType::Object), + } + } + + /// + /// Runs the optimization passes on `module`. + /// + pub fn run_optimization_passes( + &self, + module: &inkwell::module::Module, + passes: &str, + ) -> Result<(), inkwell::support::LLVMString> { + let pass_builder_options = inkwell::passes::PassBuilderOptions::create(); + pass_builder_options.set_verify_each(self.optimizer_settings.is_verify_each_enabled); + pass_builder_options.set_debug_logging(self.optimizer_settings.is_debug_logging_enabled); + + pass_builder_options.set_loop_unrolling( + self.optimizer_settings.level_middle_end_size == OptimizerSettingsSizeLevel::Zero, + ); + pass_builder_options.set_merge_functions(true); + + module.run_passes(passes, &self.target_machine, pass_builder_options) + } + + /// + /// Returns the target triple. + /// + pub fn get_triple(&self) -> inkwell::targets::TargetTriple { + self.target_machine.get_triple() + } + + /// + /// Returns the target data. + /// + pub fn get_target_data(&self) -> inkwell::targets::TargetData { + self.target_machine.get_target_data() + } +} diff --git a/crates/llvm-context/src/target_machine/target.rs b/crates/llvm-context/src/target_machine/target.rs new file mode 100644 index 0000000..fedc802 --- /dev/null +++ b/crates/llvm-context/src/target_machine/target.rs @@ -0,0 +1,66 @@ +//! +//! The LLVM target. +//! + +use std::str::FromStr; + +/// +/// The LLVM target. +/// +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Target { + /// The EraVM target. + PVM, +} + +impl Target { + /// + /// Returns the target name. + /// + pub fn name(&self) -> &str { + match self { + Self::PVM => "riscv32", + } + } + + /// + /// Returns the target triple. + /// + pub fn triple(&self) -> &str { + match self { + Self::PVM => "riscv32-unknown-unknown-elf", + } + } + + /// + /// Returns the target production name. + /// + pub fn production_name(&self) -> &str { + match self { + Self::PVM => "PVM", + } + } +} + +impl FromStr for Target { + type Err = anyhow::Error; + + fn from_str(string: &str) -> Result { + match string { + "riscv32" => Ok(Self::PVM), + _ => Err(anyhow::anyhow!( + "Unknown target `{}`. Supported targets: {:?}", + string, + Self::PVM + )), + } + } +} + +impl std::fmt::Display for Target { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Target::PVM => write!(f, "riscv32"), + } + } +} diff --git a/crates/pallet-contracts-pvm-llapi/Cargo.toml b/crates/pallet-contracts-pvm-llapi/Cargo.toml new file mode 100644 index 0000000..83afe54 --- /dev/null +++ b/crates/pallet-contracts-pvm-llapi/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "pallet-contracts-pvm-llapi" +version = "0.1.0" +edition = "2021" + +[dependencies] +inkwell = { workspace = true, no-default-features = true, features = ["target-riscv", "no-libffi-linking", "llvm16-0"] } \ No newline at end of file diff --git a/crates/pallet-contracts-pvm-llapi/README.md b/crates/pallet-contracts-pvm-llapi/README.md new file mode 100644 index 0000000..4c06a95 --- /dev/null +++ b/crates/pallet-contracts-pvm-llapi/README.md @@ -0,0 +1 @@ +# Pallet `contracts` low level API for PolkaVM diff --git a/crates/pallet-contracts-pvm-llapi/build.rs b/crates/pallet-contracts-pvm-llapi/build.rs new file mode 100644 index 0000000..110e7f0 --- /dev/null +++ b/crates/pallet-contracts-pvm-llapi/build.rs @@ -0,0 +1,47 @@ +use std::{env, fs, path::Path, process::Command}; + +fn compile(bitcode_path: &str) { + let output = Command::new("clang") + .args([ + "--target=riscv32", + "-Xclang", + "-triple=riscv32-unknown-unknown-elf", + "-march=rv32em", + "-mabi=ilp32e", + "-fno-exceptions", + "-ffreestanding", + "-Wall", + "-fno-builtin", + "-O3", + "-emit-llvm", + "-c", + "src/polkavm_guest.c", + "-o", + bitcode_path, + ]) + .output() + .expect("should be able to invoke C clang"); + + assert!( + output.status.success(), + "failed to compile the PolkaVM C API: {:?}", + output + ); +} + +fn main() { + let out_dir = env::var_os("OUT_DIR").expect("env should have $OUT_DIR"); + let lib = "polkavm_guest.bc"; + let bitcode_path = Path::new(&out_dir).join(lib); + compile(bitcode_path.to_str().expect("$OUT_DIR should be UTF-8")); + + let bitcode = fs::read(bitcode_path).expect("bitcode should have been built"); + let len = bitcode.len(); + let src_path = Path::new(&out_dir).join("polkavm_guest.rs"); + let src = format!("pub static BITCODE: &[u8; {len}] = include_bytes!(\"{lib}\");"); + fs::write(src_path, src).expect("should be able to write in $OUT_DIR"); + + println!("cargo:rerun-if-changed=build.rs"); + println!("cargo:rerun-if-changed=src/polkavm_guest.c"); + println!("cargo:rerun-if-changed=src/polkavm_guest.h"); +} diff --git a/crates/pallet-contracts-pvm-llapi/src/lib.rs b/crates/pallet-contracts-pvm-llapi/src/lib.rs new file mode 100644 index 0000000..6e9217f --- /dev/null +++ b/crates/pallet-contracts-pvm-llapi/src/lib.rs @@ -0,0 +1,42 @@ +//! This crate vendors the [PolkaVM][0] C API and provides a LLVM module for interacting +//! with the `pallet-contracts` runtime API. +//! +//! At present, the contracts pallet requires blobs to export `call` and `deploy`, +//! and offers a bunch of [runtime API methods][1]. The provided [module] implements +//! those exports and imports. +//! +//! [0]: [https://crates.io/crates/polkavm] +//! [1]: [https://docs.rs/pallet-contracts/26.0.0/pallet_contracts/api_doc/index.html] +//! + +use inkwell::{context::Context, memory_buffer::MemoryBuffer, module::Module, support::LLVMString}; + +include!(concat!(env!("OUT_DIR"), "/polkavm_guest.rs")); + +/// Creates a LLVM module from the [BITCODE]. +/// +/// The module does: +/// - Export the `call` and `deploy` functions (which are named thereafter). +/// - Import (most) `pallet-contracts` runtime API functions. +/// +/// Returns `Error` if the bitcode fails to parse, which should never happen. +pub fn module<'context>( + context: &'context Context, + module_name: &str, +) -> Result, LLVMString> { + let buf = MemoryBuffer::create_from_memory_range(BITCODE, module_name); + Module::parse_bitcode_from_buffer(&buf, context) +} + +#[cfg(test)] +mod tests { + #[test] + fn it_works() { + inkwell::targets::Target::initialize_riscv(&Default::default()); + let context = inkwell::context::Context::create(); + let module = crate::module(&context, "polkavm_guest").unwrap(); + + assert!(module.get_function("call").is_some()); + assert!(module.get_function("deploy").is_some()); + } +} diff --git a/crates/pallet-contracts-pvm-llapi/src/polkavm_guest.c b/crates/pallet-contracts-pvm-llapi/src/polkavm_guest.c new file mode 100644 index 0000000..50f1fa9 --- /dev/null +++ b/crates/pallet-contracts-pvm-llapi/src/polkavm_guest.c @@ -0,0 +1,95 @@ +#include +#include + +#include "polkavm_guest.h" + + +// Missing builtins + +void * memset(void *b, int c, size_t len) { + uint8_t *dest = b; + while (len-- > 0) *dest++ = c; + return b; +} + + +// Exports + +extern void call(); +POLKAVM_EXPORT(void, call) + +extern void deploy(); +POLKAVM_EXPORT(void, deploy) + + +// Imports + +POLKAVM_IMPORT(void, input, uint32_t, uint32_t) + +POLKAVM_IMPORT(void, seal_return, uint32_t, uint32_t, uint32_t) + +POLKAVM_IMPORT(void, value_transferred, uint32_t, uint32_t) + +POLKAVM_IMPORT(uint32_t, set_storage, uint32_t, uint32_t, uint32_t, uint32_t) + +POLKAVM_IMPORT(uint32_t, get_storage, uint32_t, uint32_t, uint32_t, uint32_t) + +POLKAVM_IMPORT(uint32_t, clear_storage, uint32_t, uint32_t) + +POLKAVM_IMPORT(uint32_t, contains_storage, uint32_t, uint32_t) + +POLKAVM_IMPORT(uint32_t, take_storage, uint32_t, uint32_t, uint32_t, uint32_t) + +POLKAVM_IMPORT(uint32_t, seal_call, uint32_t) + +POLKAVM_IMPORT(uint32_t, delegate_call, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t) + +POLKAVM_IMPORT(uint32_t, instantiate, uint32_t) + +POLKAVM_IMPORT(void, terminate, uint32_t) + +POLKAVM_IMPORT(void, caller, uint32_t, uint32_t) + +POLKAVM_IMPORT(uint32_t, is_contract, uint32_t) + +POLKAVM_IMPORT(uint32_t, code_hash, uint32_t, uint32_t, uint32_t) + +POLKAVM_IMPORT(void, own_code_hash, uint32_t, uint32_t) + +POLKAVM_IMPORT(uint32_t, caller_is_origin) + +POLKAVM_IMPORT(uint32_t, caller_is_root) + +POLKAVM_IMPORT(void, address, uint32_t, uint32_t) + +POLKAVM_IMPORT(void, weight_to_fee, uint64_t, uint32_t, uint32_t) + +POLKAVM_IMPORT(void, gas_left, uint32_t, uint32_t) + +POLKAVM_IMPORT(void, balance, uint32_t, uint32_t) + +POLKAVM_IMPORT(void, now, uint32_t, uint32_t) + +POLKAVM_IMPORT(void, minimum_balance, uint32_t, uint32_t) + +POLKAVM_IMPORT(void, deposit_event, uint32_t, uint32_t, uint32_t, uint32_t) + +POLKAVM_IMPORT(void, block_number, uint32_t, uint32_t) + +POLKAVM_IMPORT(void, hash_sha2_256, uint32_t, uint32_t, uint32_t) + +POLKAVM_IMPORT(void, hash_keccak_256, uint32_t, uint32_t, uint32_t) + +POLKAVM_IMPORT(void, hash_blake2_256, uint32_t, uint32_t, uint32_t) + +POLKAVM_IMPORT(void, hash_blake2_128, uint32_t, uint32_t, uint32_t) + +POLKAVM_IMPORT(uint32_t, call_chain_extension, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t) + +POLKAVM_IMPORT(uint32_t, debug_message, uint32_t, uint32_t) + +POLKAVM_IMPORT(uint32_t, set_code_hash, uint32_t) + +POLKAVM_IMPORT(uint64_t, instantiation_nonce,) + +POLKAVM_IMPORT(uint32_t, transfer, uint32_t, uint32_t, uint32_t, uint32_t) diff --git a/crates/pallet-contracts-pvm-llapi/src/polkavm_guest.h b/crates/pallet-contracts-pvm-llapi/src/polkavm_guest.h new file mode 100644 index 0000000..cf5b154 --- /dev/null +++ b/crates/pallet-contracts-pvm-llapi/src/polkavm_guest.h @@ -0,0 +1,165 @@ +// Vendored from: https://github.com/koute/polkavm/blob/master/capi/polkavm_guest.h + +#ifndef POLKAVM_GUEST_H_ +#define POLKAVM_GUEST_H_ + +#define POLKAVM_JOIN_IMPL(X,Y) X##Y +#define POLKAVM_JOIN(X,Y) POLKAVM_JOIN_IMPL(X, Y) +#define POLKAVM_UNIQUE(X) POLKAVM_JOIN(X, __COUNTER__) + +#define POLKAVM_REGS_FOR_TY_void 0 +#define POLKAVM_REGS_FOR_TY_i32 1 +#define POLKAVM_REGS_FOR_TY_i64 2 + +#define POLKAVM_REGS_FOR_TY_int8_t POLKAVM_REGS_FOR_TY_i32 +#define POLKAVM_REGS_FOR_TY_uint8_t POLKAVM_REGS_FOR_TY_i32 +#define POLKAVM_REGS_FOR_TY_int16_t POLKAVM_REGS_FOR_TY_i32 +#define POLKAVM_REGS_FOR_TY_uint16_t POLKAVM_REGS_FOR_TY_i32 +#define POLKAVM_REGS_FOR_TY_int32_t POLKAVM_REGS_FOR_TY_i32 +#define POLKAVM_REGS_FOR_TY_uint32_t POLKAVM_REGS_FOR_TY_i32 +#define POLKAVM_REGS_FOR_TY_int64_t POLKAVM_REGS_FOR_TY_i64 +#define POLKAVM_REGS_FOR_TY_uint64_t POLKAVM_REGS_FOR_TY_i64 +#define POLKAVM_REGS_FOR_TY_int POLKAVM_REGS_FOR_TY_i32 + +#ifdef _LP64 + #define POLKAVM_REGS_FOR_TY_size_t POLKAVM_REGS_FOR_TY_i64 + #define POLKAVM_REGS_FOR_TY_long POLKAVM_REGS_FOR_TY_i64 +#else + #define POLKAVM_REGS_FOR_TY_size_t POLKAVM_REGS_FOR_TY_i32 + #define POLKAVM_REGS_FOR_TY_long POLKAVM_REGS_FOR_TY_i32 +#endif + +#define POLKAVM_COUNT_ARGS(...) POLKAVM_COUNT_ARGS_IMPL(0, ## __VA_ARGS__, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) +#define POLKAVM_COUNT_ARGS_IMPL(_0, _1_, _2_, _3_, _4_, _5_, _6_, _7_, _8_, _9_, _10_, _11_, _12_, _13_, _14_, _15_, _16_, _17_, _18_, _19_, _20_, _21_, _22_, _23_, _24_, _25_, _26_, _27_, _28_, _29_, _30_, _31_, _32_, count, ...) count + +#define POLKAVM_REGS_FOR_TY(X) POLKAVM_JOIN(POLKAVM_REGS_FOR_TY_, X) + +#define POLKAVM_ARG_ASM_4_0() +#define POLKAVM_ARG_ASM_4_1(A) \ + ".byte %[arg_1]\n" +#define POLKAVM_ARG_ASM_4_2(A, B) \ + ".byte %[arg_1]\n" \ + ".byte %[arg_2]\n" +#define POLKAVM_ARG_ASM_4_3(A, B, C) \ + ".byte %[arg_1]\n" \ + ".byte %[arg_2]\n" \ + ".byte %[arg_3]\n" +#define POLKAVM_ARG_ASM_4_4(A, B, C, D) \ + ".byte %[arg_1]\n" \ + ".byte %[arg_2]\n" \ + ".byte %[arg_3]\n" \ + ".byte %[arg_4]\n" +#define POLKAVM_ARG_ASM_4_5(A, B, C, D, E) \ + ".byte %[arg_1]\n" \ + ".byte %[arg_2]\n" \ + ".byte %[arg_3]\n" \ + ".byte %[arg_4]\n" \ + ".byte %[arg_5]\n" +#define POLKAVM_ARG_ASM_4_6(A, B, C, D, E, F) \ + ".byte %[arg_1]\n" \ + ".byte %[arg_2]\n" \ + ".byte %[arg_3]\n" \ + ".byte %[arg_4]\n" \ + ".byte %[arg_5]\n" \ + ".byte %[arg_6]\n" + +#define POLKAVM_ARG_ASM_3(N, ...) POLKAVM_ARG_ASM_4_ ## N(__VA_ARGS__) +#define POLKAVM_ARG_ASM_2(N, ...) POLKAVM_ARG_ASM_3(N, ## __VA_ARGS__) +#define POLKAVM_ARG_ASM(...) POLKAVM_ARG_ASM_2(POLKAVM_COUNT_ARGS(__VA_ARGS__), ## __VA_ARGS__) + +#define POLKAVM_COUNT_REGS_4_0() \ + 0 +#define POLKAVM_COUNT_REGS_4_1(A) \ + POLKAVM_REGS_FOR_TY(A) +#define POLKAVM_COUNT_REGS_4_2(A, B) \ + POLKAVM_REGS_FOR_TY(A) + POLKAVM_REGS_FOR_TY(B) +#define POLKAVM_COUNT_REGS_4_3(A, B, C) \ + POLKAVM_REGS_FOR_TY(A) + POLKAVM_REGS_FOR_TY(B) + POLKAVM_REGS_FOR_TY(C) +#define POLKAVM_COUNT_REGS_4_4(A, B, C, D) \ + POLKAVM_REGS_FOR_TY(A) + POLKAVM_REGS_FOR_TY(B) + POLKAVM_REGS_FOR_TY(C) + POLKAVM_REGS_FOR_TY(D) +#define POLKAVM_COUNT_REGS_4_5(A, B, C, D, E) \ + POLKAVM_REGS_FOR_TY(A) + POLKAVM_REGS_FOR_TY(B) + POLKAVM_REGS_FOR_TY(C) + POLKAVM_REGS_FOR_TY(D) + POLKAVM_REGS_FOR_TY(E) +#define POLKAVM_COUNT_REGS_4_6(A, B, C, D, E, F) \ + POLKAVM_REGS_FOR_TY(A) + POLKAVM_REGS_FOR_TY(B) + POLKAVM_REGS_FOR_TY(C) + POLKAVM_REGS_FOR_TY(D) + POLKAVM_REGS_FOR_TY(E) + POLKAVM_REGS_FOR_TY(F) + +#define POLKAVM_COUNT_REGS_3(N, ...) POLKAVM_COUNT_REGS_4_ ## N(__VA_ARGS__) +#define POLKAVM_COUNT_REGS_2(N, ...) POLKAVM_COUNT_REGS_3(N, ## __VA_ARGS__) +#define POLKAVM_COUNT_REGS(...) POLKAVM_COUNT_REGS_2(POLKAVM_COUNT_ARGS(__VA_ARGS__), ## __VA_ARGS__) + +#define POLKAVM_IMPORT_ARGS_IMPL_4_0() +#define POLKAVM_IMPORT_ARGS_IMPL_4_1(A0) A0 a0 +#define POLKAVM_IMPORT_ARGS_IMPL_4_2(A0, A1) A0 a0, A1 a1 +#define POLKAVM_IMPORT_ARGS_IMPL_4_3(A0, A1, A2) A0 a0, A1 a1, A2 a2 +#define POLKAVM_IMPORT_ARGS_IMPL_4_4(A0, A1, A2, A3) A0 a0, A1 a1, A2 a2, A3 a3 +#define POLKAVM_IMPORT_ARGS_IMPL_4_5(A0, A1, A2, A3, A4) A0 a0, A1 a1, A2 a2, A3 a3, A4 a4 +#define POLKAVM_IMPORT_ARGS_IMPL_4_6(A0, A1, A2, A3, A4, A5) A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5 + +#define POLKAVM_IMPORT_ARGS_IMPL_3(N, ...) POLKAVM_IMPORT_ARGS_IMPL_4_ ## N(__VA_ARGS__) +#define POLKAVM_IMPORT_ARGS_IMPL_2(N, ...) POLKAVM_IMPORT_ARGS_IMPL_3(N, ## __VA_ARGS__) +#define POLKAVM_IMPORT_ARGS_IMPL(...) POLKAVM_IMPORT_ARGS_IMPL_2(POLKAVM_COUNT_ARGS(__VA_ARGS__), ## __VA_ARGS__) + +struct PolkaVM_Metadata { + unsigned char version; + unsigned int flags; + unsigned int symbol_length; + const char * symbol; + unsigned char input_regs; + unsigned char output_regs; +} __attribute__ ((packed)); + +#define POLKAVM_EXPORT(arg_return_ty, fn_name, ...) \ +static struct PolkaVM_Metadata POLKAVM_JOIN(fn_name, __EXPORT_METADATA) __attribute__ ((section(".polkavm_metadata"))) = { \ + 1, 0, sizeof(#fn_name) - 1, #fn_name, POLKAVM_COUNT_REGS(__VA_ARGS__), POLKAVM_COUNT_REGS(arg_return_ty) \ +}; \ +static void __attribute__ ((naked, used)) POLKAVM_UNIQUE(polkavm_export_dummy)() { \ + __asm__( \ + ".pushsection .polkavm_exports,\"R\",@note\n" \ + ".byte 1\n" \ + ".word %[metadata]\n" \ + ".word %[function]\n" \ + ".popsection\n" \ + : \ + : \ + [metadata] "i" (&POLKAVM_JOIN(fn_name, __EXPORT_METADATA)), \ + [function] "i" (fn_name) \ + : "memory" \ + ); \ +} + +#define POLKAVM_IMPORT(arg_return_ty, fn_name, ...) \ +static struct PolkaVM_Metadata POLKAVM_JOIN(fn_name, __IMPORT_METADATA) __attribute__ ((section(".polkavm_metadata"))) = { \ + 1, 0, sizeof(#fn_name) - 1, #fn_name, POLKAVM_COUNT_REGS(__VA_ARGS__), POLKAVM_COUNT_REGS(arg_return_ty) \ +}; \ +static arg_return_ty __attribute__ ((naked, used)) fn_name(POLKAVM_IMPORT_ARGS_IMPL(__VA_ARGS__)) { \ + __asm__( \ + ".word 0x0000000b\n" \ + ".word %[metadata]\n" \ + "ret\n" \ + : \ + : \ + [metadata] "i" (&POLKAVM_JOIN(fn_name, __IMPORT_METADATA)) \ + : "memory" \ + ); \ +} + +#define POLKAVM_MIN_STACK_SIZE(size) \ +static void __attribute__ ((naked, used)) POLKAVM_UNIQUE(polkavm_stack_size)() { \ + __asm__( \ + ".pushsection .polkavm_min_stack_size,\"\",@progbits\n" \ + ".word %[value]\n" \ + ".popsection\n" \ + : \ + : \ + [value] "i" (size) \ + : \ + ); \ +} + +#define POLKAVM_TRAP() \ +{ \ + __asm__("unimp\n" :::); \ + __builtin_unreachable(); \ +} + +#endif + \ No newline at end of file diff --git a/crates/solidity/Cargo.toml b/crates/solidity/Cargo.toml new file mode 100644 index 0000000..2984918 --- /dev/null +++ b/crates/solidity/Cargo.toml @@ -0,0 +1,44 @@ +[package] +name = "revive-solidity" +version = "1.4.0" +authors = [ + "Oleksandr Zarudnyi ", +] +license = "MIT OR Apache-2.0" +edition = "2021" +description = "EraVM Solidity compiler" + +[[bin]] +name = "zksolc" +path = "src/zksolc/main.rs" + +[lib] +doctest = false + +[dependencies] +structopt = { workspace = true } +colored = { workspace = true } +thiserror = { workspace = true } +anyhow = { workspace = true } +which = { workspace = true } +path-slash = { workspace = true } +rayon = { workspace = true } + +serde = { workspace = true } +serde_json = { workspace = true } +semver = { workspace = true } +once_cell = { workspace = true } +rand = { workspace = true } +regex = { workspace = true } +hex = { workspace = true } +num = { workspace = true } +sha3 = { workspace = true } +md5 = { workspace = true } +inkwell = { workspace = true } + +era-compiler-common = { git = "https://github.com/matter-labs/era-compiler-common", branch = "main" } +era-compiler-llvm-context = { path = "../llvm-context" } + + +[target.'cfg(target_env = "musl")'.dependencies] +mimalloc = { version = "*", default-features = false } diff --git a/crates/solidity/src/build/contract.rs b/crates/solidity/src/build/contract.rs new file mode 100644 index 0000000..871db52 --- /dev/null +++ b/crates/solidity/src/build/contract.rs @@ -0,0 +1,170 @@ +//! +//! The Solidity contract build. +//! + +use std::collections::HashSet; +use std::fs::File; +use std::io::Write; +use std::path::Path; + +use serde::Deserialize; +use serde::Serialize; + +use crate::solc::combined_json::contract::Contract as CombinedJsonContract; +use crate::solc::standard_json::output::contract::Contract as StandardJsonOutputContract; + +/// +/// The Solidity contract build. +/// +#[derive(Debug, Serialize, Deserialize)] +pub struct Contract { + /// The contract path. + pub path: String, + /// The auxiliary identifier. Used to identify Yul objects. + pub identifier: String, + /// The LLVM module build. + pub build: era_compiler_llvm_context::EraVMBuild, + /// The metadata JSON. + pub metadata_json: serde_json::Value, + /// The factory dependencies. + pub factory_dependencies: HashSet, +} + +impl Contract { + /// + /// A shortcut constructor. + /// + pub fn new( + path: String, + identifier: String, + build: era_compiler_llvm_context::EraVMBuild, + metadata_json: serde_json::Value, + factory_dependencies: HashSet, + ) -> Self { + Self { + path, + identifier, + build, + metadata_json, + factory_dependencies, + } + } + + /// + /// Writes the contract text assembly and bytecode to files. + /// + pub fn write_to_directory( + self, + path: &Path, + output_assembly: bool, + output_binary: bool, + overwrite: bool, + ) -> anyhow::Result<()> { + let file_name = Self::short_path(self.path.as_str()); + + if output_assembly { + let file_name = format!( + "{}.{}", + file_name, + era_compiler_common::EXTENSION_ERAVM_ASSEMBLY + ); + let mut file_path = path.to_owned(); + file_path.push(file_name); + + if file_path.exists() && !overwrite { + eprintln!( + "Refusing to overwrite an existing file {file_path:?} (use --overwrite to force)." + ); + } else { + File::create(&file_path) + .map_err(|error| { + anyhow::anyhow!("File {:?} creating error: {}", file_path, error) + })? + .write_all(self.build.assembly_text.as_bytes()) + .map_err(|error| { + anyhow::anyhow!("File {:?} writing error: {}", file_path, error) + })?; + } + } + + if output_binary { + let file_name = format!( + "{}.{}", + file_name, + era_compiler_common::EXTENSION_ERAVM_BINARY + ); + let mut file_path = path.to_owned(); + file_path.push(file_name); + + if file_path.exists() && !overwrite { + eprintln!( + "Refusing to overwrite an existing file {file_path:?} (use --overwrite to force)." + ); + } else { + File::create(&file_path) + .map_err(|error| { + anyhow::anyhow!("File {:?} creating error: {}", file_path, error) + })? + .write_all(self.build.bytecode.as_slice()) + .map_err(|error| { + anyhow::anyhow!("File {:?} writing error: {}", file_path, error) + })?; + } + } + + Ok(()) + } + + /// + /// Writes the contract text assembly and bytecode to the combined JSON. + /// + pub fn write_to_combined_json( + self, + combined_json_contract: &mut CombinedJsonContract, + ) -> anyhow::Result<()> { + if let Some(metadata) = combined_json_contract.metadata.as_mut() { + *metadata = self.metadata_json.to_string(); + } + + if let Some(asm) = combined_json_contract.asm.as_mut() { + *asm = serde_json::Value::String(self.build.assembly_text); + } + let hexadecimal_bytecode = hex::encode(self.build.bytecode); + combined_json_contract.bin = Some(hexadecimal_bytecode); + combined_json_contract.bin_runtime = combined_json_contract.bin.clone(); + + combined_json_contract.factory_deps = Some(self.build.factory_dependencies); + + Ok(()) + } + + /// + /// Writes the contract text assembly and bytecode to the standard JSON. + /// + pub fn write_to_standard_json( + self, + standard_json_contract: &mut StandardJsonOutputContract, + ) -> anyhow::Result<()> { + standard_json_contract.metadata = Some(self.metadata_json); + + let assembly_text = self.build.assembly_text; + let bytecode = hex::encode(self.build.bytecode.as_slice()); + if let Some(evm) = standard_json_contract.evm.as_mut() { + evm.modify(assembly_text, bytecode); + } + + standard_json_contract.factory_dependencies = Some(self.build.factory_dependencies); + standard_json_contract.hash = Some(self.build.bytecode_hash); + + Ok(()) + } + + /// + /// Converts the full path to a short one. + /// + pub fn short_path(path: &str) -> &str { + path.rfind('/') + .map(|last_slash| &path[last_slash + 1..]) + .unwrap_or_else(|| path) + } +} diff --git a/crates/solidity/src/build/mod.rs b/crates/solidity/src/build/mod.rs new file mode 100644 index 0000000..f031262 --- /dev/null +++ b/crates/solidity/src/build/mod.rs @@ -0,0 +1,107 @@ +//! +//! The Solidity project build. +//! + +pub mod contract; + +use std::collections::BTreeMap; +use std::path::Path; + +use crate::solc::combined_json::CombinedJson; +use crate::solc::standard_json::output::Output as StandardJsonOutput; +use crate::solc::version::Version as SolcVersion; + +use self::contract::Contract; + +/// +/// The Solidity project build. +/// +#[derive(Debug, Default)] +pub struct Build { + /// The contract data, + pub contracts: BTreeMap, +} + +impl Build { + /// + /// Writes all contracts to the specified directory. + /// + pub fn write_to_directory( + self, + output_directory: &Path, + output_assembly: bool, + output_binary: bool, + overwrite: bool, + ) -> anyhow::Result<()> { + for (_path, contract) in self.contracts.into_iter() { + contract.write_to_directory( + output_directory, + output_assembly, + output_binary, + overwrite, + )?; + } + + Ok(()) + } + + /// + /// Writes all contracts assembly and bytecode to the combined JSON. + /// + pub fn write_to_combined_json( + self, + combined_json: &mut CombinedJson, + zksolc_version: &semver::Version, + ) -> anyhow::Result<()> { + for (path, contract) in self.contracts.into_iter() { + let combined_json_contract = combined_json + .contracts + .iter_mut() + .find_map(|(json_path, contract)| { + if path.ends_with(json_path) { + Some(contract) + } else { + None + } + }) + .ok_or_else(|| anyhow::anyhow!("Contract `{}` not found in the project", path))?; + + contract.write_to_combined_json(combined_json_contract)?; + } + + combined_json.zk_version = Some(zksolc_version.to_string()); + + Ok(()) + } + + /// + /// Writes all contracts assembly and bytecode to the standard JSON. + /// + pub fn write_to_standard_json( + mut self, + standard_json: &mut StandardJsonOutput, + solc_version: &SolcVersion, + zksolc_version: &semver::Version, + ) -> anyhow::Result<()> { + let contracts = match standard_json.contracts.as_mut() { + Some(contracts) => contracts, + None => return Ok(()), + }; + + for (path, contracts) in contracts.iter_mut() { + for (name, contract) in contracts.iter_mut() { + let full_name = format!("{path}:{name}"); + + if let Some(contract_data) = self.contracts.remove(full_name.as_str()) { + contract_data.write_to_standard_json(contract)?; + } + } + } + + standard_json.version = Some(solc_version.default.to_string()); + standard_json.long_version = Some(solc_version.long.to_owned()); + standard_json.zk_version = Some(zksolc_version.to_string()); + + Ok(()) + } +} diff --git a/crates/solidity/src/const.rs b/crates/solidity/src/const.rs new file mode 100644 index 0000000..fc7905e --- /dev/null +++ b/crates/solidity/src/const.rs @@ -0,0 +1,20 @@ +//! +//! Solidity to EraVM compiler constants. +//! + +#![allow(dead_code)] + +/// The default executable name. +pub static DEFAULT_EXECUTABLE_NAME: &str = "zksolc"; + +/// The `keccak256` scratch space offset. +pub const OFFSET_SCRATCH_SPACE: usize = 0; + +/// The memory pointer offset. +pub const OFFSET_MEMORY_POINTER: usize = 2 * era_compiler_common::BYTE_LENGTH_FIELD; + +/// The empty slot offset. +pub const OFFSET_EMPTY_SLOT: usize = 3 * era_compiler_common::BYTE_LENGTH_FIELD; + +/// The non-reserved memory offset. +pub const OFFSET_NON_RESERVED: usize = 4 * era_compiler_common::BYTE_LENGTH_FIELD; diff --git a/crates/solidity/src/evmla/assembly/data.rs b/crates/solidity/src/evmla/assembly/data.rs new file mode 100644 index 0000000..bb96d9a --- /dev/null +++ b/crates/solidity/src/evmla/assembly/data.rs @@ -0,0 +1,79 @@ +//! +//! The inner JSON legacy assembly code element. +//! + +use std::collections::HashSet; + +use serde::Deserialize; +use serde::Serialize; + +use crate::evmla::assembly::Assembly; + +/// +/// The inner JSON legacy assembly code element. +/// +#[derive(Debug, Deserialize, Serialize, Clone)] +#[serde(untagged)] +pub enum Data { + /// The assembly code wrapper. + Assembly(Assembly), + /// The hash. + Hash(String), + /// The full contract path after the factory dependencies replacing pass. + Path(String), +} + +impl Data { + /// + /// Returns the inner assembly reference if it is present. + /// + pub fn get_assembly(&self) -> Option<&Assembly> { + match self { + Self::Assembly(ref assembly) => Some(assembly), + Self::Hash(_) => None, + Self::Path(_) => None, + } + } + /// + /// Returns the inner assembly mutable reference if it is present. + /// + pub fn get_assembly_mut(&mut self) -> Option<&mut Assembly> { + match self { + Self::Assembly(ref mut assembly) => Some(assembly), + Self::Hash(_) => None, + Self::Path(_) => None, + } + } + + /// + /// Get the list of missing deployable libraries. + /// + pub fn get_missing_libraries(&self) -> HashSet { + match self { + Self::Assembly(assembly) => assembly.get_missing_libraries(), + Self::Hash(_) => HashSet::new(), + Self::Path(_) => HashSet::new(), + } + } + + /// + /// Gets the contract `keccak256` hash. + /// + pub fn keccak256(&self) -> String { + match self { + Self::Assembly(assembly) => assembly.keccak256(), + Self::Hash(hash) => panic!("Expected assembly, found hash `{hash}`"), + Self::Path(path) => panic!("Expected assembly, found path `{path}`"), + } + } +} + +impl std::fmt::Display for Data { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Assembly(inner) => writeln!(f, "{inner}"), + Self::Hash(inner) => writeln!(f, "Hash `{inner}`"), + Self::Path(inner) => writeln!(f, "Path `{inner}`"), + } + } +} diff --git a/crates/solidity/src/evmla/assembly/instruction/codecopy.rs b/crates/solidity/src/evmla/assembly/instruction/codecopy.rs new file mode 100644 index 0000000..291e29d --- /dev/null +++ b/crates/solidity/src/evmla/assembly/instruction/codecopy.rs @@ -0,0 +1,88 @@ +//! +//! Translates the CODECOPY use cases. +//! + +/// +/// Translates the contract hash copying. +/// +pub fn contract_hash<'ctx, D>( + context: &mut era_compiler_llvm_context::EraVMContext<'ctx, D>, + offset: inkwell::values::IntValue<'ctx>, + value: inkwell::values::IntValue<'ctx>, +) -> anyhow::Result<()> +where + D: era_compiler_llvm_context::EraVMDependency + Clone, +{ + let offset = context.builder().build_int_add( + offset, + context.field_const( + (era_compiler_common::BYTE_LENGTH_X32 + era_compiler_common::BYTE_LENGTH_FIELD) as u64, + ), + "datacopy_contract_hash_offset", + )?; + + era_compiler_llvm_context::eravm_evm_memory::store(context, offset, value)?; + + Ok(()) +} + +/// +/// Translates the library marker copying. +/// +pub fn library_marker( + context: &mut era_compiler_llvm_context::EraVMContext, + offset: u64, + value: u64, +) -> anyhow::Result<()> +where + D: era_compiler_llvm_context::EraVMDependency + Clone, +{ + era_compiler_llvm_context::eravm_evm_memory::store_byte( + context, + context.field_const(offset), + context.field_const(value), + )?; + + Ok(()) +} + +/// +/// Translates the static data copying. +/// +pub fn static_data<'ctx, D>( + context: &mut era_compiler_llvm_context::EraVMContext<'ctx, D>, + destination: inkwell::values::IntValue<'ctx>, + source: &str, +) -> anyhow::Result<()> +where + D: era_compiler_llvm_context::EraVMDependency + Clone, +{ + let mut offset = 0; + for (index, chunk) in source + .chars() + .collect::>() + .chunks(era_compiler_common::BYTE_LENGTH_FIELD * 2) + .enumerate() + { + let mut value_string = chunk.iter().collect::(); + value_string.push_str( + "0".repeat((era_compiler_common::BYTE_LENGTH_FIELD * 2) - chunk.len()) + .as_str(), + ); + + let datacopy_destination = context.builder().build_int_add( + destination, + context.field_const(offset as u64), + format!("datacopy_destination_index_{index}").as_str(), + )?; + let datacopy_value = context.field_const_str_hex(value_string.as_str()); + era_compiler_llvm_context::eravm_evm_memory::store( + context, + datacopy_destination, + datacopy_value, + )?; + offset += chunk.len() / 2; + } + + Ok(()) +} diff --git a/crates/solidity/src/evmla/assembly/instruction/jump.rs b/crates/solidity/src/evmla/assembly/instruction/jump.rs new file mode 100644 index 0000000..ab11410 --- /dev/null +++ b/crates/solidity/src/evmla/assembly/instruction/jump.rs @@ -0,0 +1,75 @@ +//! +//! Translates the jump operations. +//! + +/// +/// Translates the unconditional jump. +/// +pub fn unconditional( + context: &mut era_compiler_llvm_context::EraVMContext, + destination: num::BigUint, + stack_hash: md5::Digest, +) -> anyhow::Result<()> +where + D: era_compiler_llvm_context::EraVMDependency + Clone, +{ + let code_type = context + .code_type() + .ok_or_else(|| anyhow::anyhow!("The contract code part type is undefined"))?; + let block_key = era_compiler_llvm_context::EraVMFunctionBlockKey::new(code_type, destination); + + let block = context + .current_function() + .borrow() + .evmla() + .find_block(&block_key, &stack_hash)?; + context.build_unconditional_branch(block.inner()); + + Ok(()) +} + +/// +/// Translates the conditional jump. +/// +pub fn conditional( + context: &mut era_compiler_llvm_context::EraVMContext, + destination: num::BigUint, + stack_hash: md5::Digest, + stack_height: usize, +) -> anyhow::Result<()> +where + D: era_compiler_llvm_context::EraVMDependency + Clone, +{ + let code_type = context + .code_type() + .ok_or_else(|| anyhow::anyhow!("The contract code part type is undefined"))?; + let block_key = era_compiler_llvm_context::EraVMFunctionBlockKey::new(code_type, destination); + + let condition_pointer = context.evmla().stack[stack_height] + .to_llvm() + .into_pointer_value(); + let condition = context.build_load( + era_compiler_llvm_context::EraVMPointer::new_stack_field(context, condition_pointer), + format!("conditional_{block_key}_condition").as_str(), + )?; + let condition = context.builder().build_int_compare( + inkwell::IntPredicate::NE, + condition.into_int_value(), + context.field_const(0), + format!("conditional_{block_key}_condition_compared").as_str(), + )?; + + let then_block = context + .current_function() + .borrow() + .evmla() + .find_block(&block_key, &stack_hash)?; + let join_block = + context.append_basic_block(format!("conditional_{block_key}_join_block").as_str()); + + context.build_conditional_branch(condition, then_block.inner(), join_block)?; + + context.set_basic_block(join_block); + + Ok(()) +} diff --git a/crates/solidity/src/evmla/assembly/instruction/mod.rs b/crates/solidity/src/evmla/assembly/instruction/mod.rs new file mode 100644 index 0000000..8e4be91 --- /dev/null +++ b/crates/solidity/src/evmla/assembly/instruction/mod.rs @@ -0,0 +1,398 @@ +//! +//! The EVM instruction. +//! + +pub mod codecopy; +pub mod jump; +pub mod name; +pub mod stack; + +use std::collections::BTreeMap; + +use serde::Deserialize; +use serde::Serialize; + +use self::name::Name; + +/// +/// The EVM instruction. +/// +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct Instruction { + /// The opcode or tag identifier. + pub name: Name, + /// The optional value argument. + pub value: Option, + + /// The source code identifier. + #[serde(skip_serializing_if = "Option::is_none")] + pub source: Option, + /// The source code location begin. + pub begin: isize, + /// The source code location end. + pub end: isize, +} + +impl Instruction { + /// + /// Returns the number of input stack arguments. + /// + pub const fn input_size(&self, version: &semver::Version) -> usize { + match self.name { + Name::POP => 1, + + Name::JUMP => 1, + Name::JUMPI => 2, + + Name::ADD => 2, + Name::SUB => 2, + Name::MUL => 2, + Name::DIV => 2, + Name::MOD => 2, + Name::SDIV => 2, + Name::SMOD => 2, + + Name::LT => 2, + Name::GT => 2, + Name::EQ => 2, + Name::ISZERO => 1, + Name::SLT => 2, + Name::SGT => 2, + + Name::OR => 2, + Name::XOR => 2, + Name::NOT => 1, + Name::AND => 2, + Name::SHL => 2, + Name::SHR => 2, + Name::SAR => 2, + Name::BYTE => 2, + + Name::ADDMOD => 3, + Name::MULMOD => 3, + Name::EXP => 2, + Name::SIGNEXTEND => 2, + Name::SHA3 => 2, + Name::KECCAK256 => 2, + + Name::MLOAD => 1, + Name::MSTORE => 2, + Name::MSTORE8 => 2, + Name::MCOPY => 3, + + Name::SLOAD => 1, + Name::SSTORE => 2, + Name::TLOAD => 1, + Name::TSTORE => 2, + Name::PUSHIMMUTABLE => 0, + Name::ASSIGNIMMUTABLE => { + if version.minor >= 8 { + 2 + } else { + 1 + } + } + + Name::CALLDATALOAD => 1, + Name::CALLDATACOPY => 3, + Name::CODECOPY => 3, + Name::RETURNDATACOPY => 3, + Name::EXTCODESIZE => 1, + Name::EXTCODEHASH => 1, + + Name::CALL => 7, + Name::CALLCODE => 7, + Name::STATICCALL => 6, + Name::DELEGATECALL => 6, + + Name::RETURN => 2, + Name::REVERT => 2, + Name::SELFDESTRUCT => 1, + + Name::LOG0 => 2, + Name::LOG1 => 3, + Name::LOG2 => 4, + Name::LOG3 => 5, + Name::LOG4 => 6, + + Name::CREATE => 3, + Name::CREATE2 => 4, + + Name::ZK_CREATE => 3, + Name::ZK_CREATE2 => 4, + + Name::BALANCE => 1, + + Name::BLOCKHASH => 1, + Name::BLOBHASH => 1, + + Name::EXTCODECOPY => 4, + + Name::RecursiveCall { input_size, .. } => input_size, + Name::RecursiveReturn { input_size } => input_size, + + _ => 0, + } + } + + /// + /// Returns the number of output stack arguments. + /// + pub const fn output_size(&self) -> usize { + match self.name { + Name::PUSH => 1, + Name::PUSH_Data => 1, + Name::PUSH_Tag => 1, + Name::PUSH_ContractHash => 1, + Name::PUSH_ContractHashSize => 1, + Name::PUSHLIB => 1, + Name::PUSHDEPLOYADDRESS => 1, + + Name::PUSH1 => 1, + Name::PUSH2 => 1, + Name::PUSH3 => 1, + Name::PUSH4 => 1, + Name::PUSH5 => 1, + Name::PUSH6 => 1, + Name::PUSH7 => 1, + Name::PUSH8 => 1, + Name::PUSH9 => 1, + Name::PUSH10 => 1, + Name::PUSH11 => 1, + Name::PUSH12 => 1, + Name::PUSH13 => 1, + Name::PUSH14 => 1, + Name::PUSH15 => 1, + Name::PUSH16 => 1, + Name::PUSH17 => 1, + Name::PUSH18 => 1, + Name::PUSH19 => 1, + Name::PUSH20 => 1, + Name::PUSH21 => 1, + Name::PUSH22 => 1, + Name::PUSH23 => 1, + Name::PUSH24 => 1, + Name::PUSH25 => 1, + Name::PUSH26 => 1, + Name::PUSH27 => 1, + Name::PUSH28 => 1, + Name::PUSH29 => 1, + Name::PUSH30 => 1, + Name::PUSH31 => 1, + Name::PUSH32 => 1, + + Name::DUP1 => 1, + Name::DUP2 => 1, + Name::DUP3 => 1, + Name::DUP4 => 1, + Name::DUP5 => 1, + Name::DUP6 => 1, + Name::DUP7 => 1, + Name::DUP8 => 1, + Name::DUP9 => 1, + Name::DUP10 => 1, + Name::DUP11 => 1, + Name::DUP12 => 1, + Name::DUP13 => 1, + Name::DUP14 => 1, + Name::DUP15 => 1, + Name::DUP16 => 1, + + Name::ADD => 1, + Name::SUB => 1, + Name::MUL => 1, + Name::DIV => 1, + Name::MOD => 1, + Name::SDIV => 1, + Name::SMOD => 1, + + Name::LT => 1, + Name::GT => 1, + Name::EQ => 1, + Name::ISZERO => 1, + Name::SLT => 1, + Name::SGT => 1, + + Name::OR => 1, + Name::XOR => 1, + Name::NOT => 1, + Name::AND => 1, + Name::SHL => 1, + Name::SHR => 1, + Name::SAR => 1, + Name::BYTE => 1, + + Name::ADDMOD => 1, + Name::MULMOD => 1, + Name::EXP => 1, + Name::SIGNEXTEND => 1, + Name::SHA3 => 1, + Name::KECCAK256 => 1, + + Name::MLOAD => 1, + + Name::SLOAD => 1, + Name::TLOAD => 1, + Name::PUSHIMMUTABLE => 1, + + Name::CALLDATALOAD => 1, + Name::CALLDATASIZE => 1, + Name::CODESIZE => 1, + Name::PUSHSIZE => 1, + Name::RETURNDATASIZE => 1, + Name::EXTCODESIZE => 1, + Name::EXTCODEHASH => 1, + + Name::CALL => 1, + Name::CALLCODE => 1, + Name::STATICCALL => 1, + Name::DELEGATECALL => 1, + + Name::CREATE => 1, + Name::CREATE2 => 1, + + Name::ZK_CREATE => 1, + Name::ZK_CREATE2 => 1, + + Name::ADDRESS => 1, + Name::CALLER => 1, + Name::TIMESTAMP => 1, + Name::NUMBER => 1, + + Name::CALLVALUE => 1, + Name::GAS => 1, + Name::BALANCE => 1, + Name::SELFBALANCE => 1, + + Name::GASLIMIT => 1, + Name::GASPRICE => 1, + Name::ORIGIN => 1, + Name::CHAINID => 1, + Name::BLOCKHASH => 1, + Name::BLOBHASH => 1, + Name::DIFFICULTY => 1, + Name::PREVRANDAO => 1, + Name::COINBASE => 1, + Name::MSIZE => 1, + + Name::BASEFEE => 1, + Name::BLOBBASEFEE => 1, + Name::PC => 1, + + Name::RecursiveCall { output_size, .. } => output_size, + + _ => 0, + } + } + + /// + /// Replaces the instruction data aliases with the actual data. + /// + pub fn replace_data_aliases( + instructions: &mut [Self], + mapping: &BTreeMap, + ) -> anyhow::Result<()> { + for instruction in instructions.iter_mut() { + match instruction { + Instruction { + name: Name::PUSH_ContractHash | Name::PUSH_ContractHashSize, + value: Some(value), + .. + } => { + *value = mapping.get(value.as_str()).cloned().ok_or_else(|| { + anyhow::anyhow!("Contract alias `{}` data not found", value) + })?; + } + Instruction { + name: Name::PUSH_Data, + value: Some(value), + .. + } => { + let mut key_extended = + "0".repeat(era_compiler_common::BYTE_LENGTH_FIELD * 2 - value.len()); + key_extended.push_str(value.as_str()); + + *value = mapping.get(key_extended.as_str()).cloned().ok_or_else(|| { + anyhow::anyhow!("Data chunk alias `{}` data not found", key_extended) + })?; + } + _ => {} + } + } + + Ok(()) + } + + /// + /// Initializes an `INVALID` instruction to terminate an invalid unreachable block part. + /// + pub fn invalid(previous: &Self) -> Self { + Self { + name: Name::INVALID, + value: None, + + source: previous.source, + begin: previous.begin, + end: previous.end, + } + } + + /// + /// Initializes a recursive function `Call` instruction. + /// + pub fn recursive_call( + name: String, + entry_key: era_compiler_llvm_context::EraVMFunctionBlockKey, + stack_hash: md5::Digest, + input_size: usize, + output_size: usize, + return_address: era_compiler_llvm_context::EraVMFunctionBlockKey, + previous: &Self, + ) -> Self { + Self { + name: Name::RecursiveCall { + name, + entry_key, + stack_hash, + input_size, + output_size, + return_address, + }, + value: None, + + source: previous.source, + begin: previous.begin, + end: previous.end, + } + } + + /// + /// Initializes a recursive function `Return` instruction. + /// + pub fn recursive_return(input_size: usize, previous: &Self) -> Self { + Self { + name: Name::RecursiveReturn { input_size }, + value: None, + + source: previous.source, + begin: previous.begin, + end: previous.end, + } + } +} + +impl std::fmt::Display for Instruction { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let name = self.name.to_string(); + match self.name { + Name::Tag => write!(f, "{:4}", name), + _ => write!(f, "{:15}", name), + }?; + match self.value { + Some(ref value) if value.len() <= 64 => write!(f, "{}", value)?, + Some(ref value) => write!(f, "... {}", &value[value.len() - 60..])?, + None => {} + } + Ok(()) + } +} diff --git a/crates/solidity/src/evmla/assembly/instruction/name.rs b/crates/solidity/src/evmla/assembly/instruction/name.rs new file mode 100644 index 0000000..f6ee6a8 --- /dev/null +++ b/crates/solidity/src/evmla/assembly/instruction/name.rs @@ -0,0 +1,421 @@ +//! +//! The EVM instruction name. +//! + +use serde::Deserialize; +use serde::Serialize; + +/// +/// The EVM instruction name. +/// +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)] +#[allow(non_camel_case_types)] +#[allow(clippy::upper_case_acronyms)] +pub enum Name { + /// The eponymous EVM instruction. + PUSH, + /// Pushes a constant tag index. + #[serde(rename = "PUSH [tag]")] + PUSH_Tag, + /// Pushes an unknown `data` value. + #[serde(rename = "PUSH data")] + PUSH_Data, + /// Pushes a contract hash size. + #[serde(rename = "PUSH #[$]")] + PUSH_ContractHashSize, + /// Pushes a contract hash. + #[serde(rename = "PUSH [$]")] + PUSH_ContractHash, + + /// The eponymous EVM instruction. + PUSH1, + /// The eponymous EVM instruction. + PUSH2, + /// The eponymous EVM instruction. + PUSH3, + /// The eponymous EVM instruction. + PUSH4, + /// The eponymous EVM instruction. + PUSH5, + /// The eponymous EVM instruction. + PUSH6, + /// The eponymous EVM instruction. + PUSH7, + /// The eponymous EVM instruction. + PUSH8, + /// The eponymous EVM instruction. + PUSH9, + /// The eponymous EVM instruction. + PUSH10, + /// The eponymous EVM instruction. + PUSH11, + /// The eponymous EVM instruction. + PUSH12, + /// The eponymous EVM instruction. + PUSH13, + /// The eponymous EVM instruction. + PUSH14, + /// The eponymous EVM instruction. + PUSH15, + /// The eponymous EVM instruction. + PUSH16, + /// The eponymous EVM instruction. + PUSH17, + /// The eponymous EVM instruction. + PUSH18, + /// The eponymous EVM instruction. + PUSH19, + /// The eponymous EVM instruction. + PUSH20, + /// The eponymous EVM instruction. + PUSH21, + /// The eponymous EVM instruction. + PUSH22, + /// The eponymous EVM instruction. + PUSH23, + /// The eponymous EVM instruction. + PUSH24, + /// The eponymous EVM instruction. + PUSH25, + /// The eponymous EVM instruction. + PUSH26, + /// The eponymous EVM instruction. + PUSH27, + /// The eponymous EVM instruction. + PUSH28, + /// The eponymous EVM instruction. + PUSH29, + /// The eponymous EVM instruction. + PUSH30, + /// The eponymous EVM instruction. + PUSH31, + /// The eponymous EVM instruction. + PUSH32, + + /// The eponymous EVM instruction. + DUP1, + /// The eponymous EVM instruction. + DUP2, + /// The eponymous EVM instruction. + DUP3, + /// The eponymous EVM instruction. + DUP4, + /// The eponymous EVM instruction. + DUP5, + /// The eponymous EVM instruction. + DUP6, + /// The eponymous EVM instruction. + DUP7, + /// The eponymous EVM instruction. + DUP8, + /// The eponymous EVM instruction. + DUP9, + /// The eponymous EVM instruction. + DUP10, + /// The eponymous EVM instruction. + DUP11, + /// The eponymous EVM instruction. + DUP12, + /// The eponymous EVM instruction. + DUP13, + /// The eponymous EVM instruction. + DUP14, + /// The eponymous EVM instruction. + DUP15, + /// The eponymous EVM instruction. + DUP16, + + /// The eponymous EVM instruction. + SWAP1, + /// The eponymous EVM instruction. + SWAP2, + /// The eponymous EVM instruction. + SWAP3, + /// The eponymous EVM instruction. + SWAP4, + /// The eponymous EVM instruction. + SWAP5, + /// The eponymous EVM instruction. + SWAP6, + /// The eponymous EVM instruction. + SWAP7, + /// The eponymous EVM instruction. + SWAP8, + /// The eponymous EVM instruction. + SWAP9, + /// The eponymous EVM instruction. + SWAP10, + /// The eponymous EVM instruction. + SWAP11, + /// The eponymous EVM instruction. + SWAP12, + /// The eponymous EVM instruction. + SWAP13, + /// The eponymous EVM instruction. + SWAP14, + /// The eponymous EVM instruction. + SWAP15, + /// The eponymous EVM instruction. + SWAP16, + + /// The eponymous EVM instruction. + POP, + + /// Sets the current basic code block. + #[serde(rename = "tag")] + Tag, + /// The eponymous EVM instruction. + JUMP, + /// The eponymous EVM instruction. + JUMPI, + /// The eponymous EVM instruction. + JUMPDEST, + + /// The eponymous EVM instruction. + ADD, + /// The eponymous EVM instruction. + SUB, + /// The eponymous EVM instruction. + MUL, + /// The eponymous EVM instruction. + DIV, + /// The eponymous EVM instruction. + MOD, + /// The eponymous EVM instruction. + SDIV, + /// The eponymous EVM instruction. + SMOD, + + /// The eponymous EVM instruction. + LT, + /// The eponymous EVM instruction. + GT, + /// The eponymous EVM instruction. + EQ, + /// The eponymous EVM instruction. + ISZERO, + /// The eponymous EVM instruction. + SLT, + /// The eponymous EVM instruction. + SGT, + + /// The eponymous EVM instruction. + OR, + /// The eponymous EVM instruction. + XOR, + /// The eponymous EVM instruction. + NOT, + /// The eponymous EVM instruction. + AND, + /// The eponymous EVM instruction. + SHL, + /// The eponymous EVM instruction. + SHR, + /// The eponymous EVM instruction. + SAR, + /// The eponymous EVM instruction. + BYTE, + + /// The eponymous EVM instruction. + ADDMOD, + /// The eponymous EVM instruction. + MULMOD, + /// The eponymous EVM instruction. + EXP, + /// The eponymous EVM instruction. + SIGNEXTEND, + /// The eponymous EVM instruction. + SHA3, + /// The eponymous EVM instruction. + KECCAK256, + + /// The eponymous EVM instruction. + MLOAD, + /// The eponymous EVM instruction. + MSTORE, + /// The eponymous EVM instruction. + MSTORE8, + /// The eponymous EVM instruction. + MCOPY, + + /// The eponymous EVM instruction. + SLOAD, + /// The eponymous EVM instruction. + SSTORE, + /// The eponymous EVM instruction. + TLOAD, + /// The eponymous EVM instruction. + TSTORE, + /// The eponymous EVM instruction. + PUSHIMMUTABLE, + /// The eponymous EVM instruction. + ASSIGNIMMUTABLE, + + /// The eponymous EVM instruction. + CALLDATALOAD, + /// The eponymous EVM instruction. + CALLDATASIZE, + /// The eponymous EVM instruction. + CALLDATACOPY, + /// The eponymous EVM instruction. + CODESIZE, + /// The eponymous EVM instruction. + CODECOPY, + /// The eponymous EVM instruction. + PUSHSIZE, + /// The eponymous EVM instruction. + EXTCODESIZE, + /// The eponymous EVM instruction. + EXTCODEHASH, + /// The eponymous EVM instruction. + RETURNDATASIZE, + /// The eponymous EVM instruction. + RETURNDATACOPY, + + /// The eponymous EVM instruction. + RETURN, + /// The eponymous EVM instruction. + REVERT, + /// The eponymous EVM instruction. + STOP, + /// The eponymous EVM instruction. + INVALID, + + /// The eponymous EVM instruction. + LOG0, + /// The eponymous EVM instruction. + LOG1, + /// The eponymous EVM instruction. + LOG2, + /// The eponymous EVM instruction. + LOG3, + /// The eponymous EVM instruction. + LOG4, + + /// The eponymous EVM instruction. + CALL, + /// The eponymous EVM instruction. + STATICCALL, + /// The eponymous EVM instruction. + DELEGATECALL, + + /// The eponymous EVM instruction. + CREATE, + /// The eponymous EVM instruction. + CREATE2, + + /// The eponymous EraVM instruction. + #[serde(rename = "$ZK_CREATE")] + ZK_CREATE, + /// The eponymous EraVM instruction. + #[serde(rename = "$ZK_CREATE2")] + ZK_CREATE2, + + /// The eponymous EVM instruction. + ADDRESS, + /// The eponymous EVM instruction. + CALLER, + + /// The eponymous EVM instruction. + CALLVALUE, + /// The eponymous EVM instruction. + GAS, + /// The eponymous EVM instruction. + BALANCE, + /// The eponymous EVM instruction. + SELFBALANCE, + + /// The eponymous EVM instruction. + PUSHLIB, + /// The eponymous EVM instruction. + PUSHDEPLOYADDRESS, + + /// The eponymous EVM instruction. + GASLIMIT, + /// The eponymous EVM instruction. + GASPRICE, + /// The eponymous EVM instruction. + ORIGIN, + /// The eponymous EVM instruction. + CHAINID, + /// The eponymous EVM instruction. + TIMESTAMP, + /// The eponymous EVM instruction. + NUMBER, + /// The eponymous EVM instruction. + BLOCKHASH, + /// The eponymous EVM instruction. + BLOBHASH, + /// The eponymous EVM instruction. + DIFFICULTY, + /// The eponymous EVM instruction. + PREVRANDAO, + /// The eponymous EVM instruction. + COINBASE, + /// The eponymous EVM instruction. + BASEFEE, + /// The eponymous EVM instruction. + BLOBBASEFEE, + /// The eponymous EVM instruction. + MSIZE, + + /// The eponymous EVM instruction. + CALLCODE, + /// The eponymous EVM instruction. + PC, + /// The eponymous EVM instruction. + EXTCODECOPY, + /// The eponymous EVM instruction. + SELFDESTRUCT, + + /// The recursive function call instruction. + #[serde(skip)] + RecursiveCall { + /// The called function name. + name: String, + /// The called function key. + entry_key: era_compiler_llvm_context::EraVMFunctionBlockKey, + /// The stack state hash after return. + stack_hash: md5::Digest, + /// The input size. + input_size: usize, + /// The output size. + output_size: usize, + /// The return address. + return_address: era_compiler_llvm_context::EraVMFunctionBlockKey, + }, + /// The recursive function return instruction. + #[serde(skip)] + RecursiveReturn { + /// The output size. + input_size: usize, + }, +} + +impl std::fmt::Display for Name { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Tag => write!(f, "Tag"), + Self::RecursiveCall { + name, + entry_key, + input_size, + output_size, + return_address, + .. + } => write!( + f, + "RECURSIVE_CALL({}_{}, {}, {}, {})", + name, entry_key, input_size, output_size, return_address + ), + Self::RecursiveReturn { input_size } => write!(f, "RECURSIVE_RETURN({})", input_size), + _ => write!( + f, + "{}", + serde_json::to_string(self) + .expect("Always valid") + .trim_matches('\"') + ), + } + } +} diff --git a/crates/solidity/src/evmla/assembly/instruction/stack.rs b/crates/solidity/src/evmla/assembly/instruction/stack.rs new file mode 100644 index 0000000..fde1988 --- /dev/null +++ b/crates/solidity/src/evmla/assembly/instruction/stack.rs @@ -0,0 +1,114 @@ +//! +//! Translates the stack memory operations. +//! + +use inkwell::values::BasicValue; + +/// +/// Translates the ordinar value push. +/// +pub fn push<'ctx, D>( + context: &mut era_compiler_llvm_context::EraVMContext<'ctx, D>, + value: String, +) -> anyhow::Result> +where + D: era_compiler_llvm_context::EraVMDependency + Clone, +{ + let result = context + .field_type() + .const_int_from_string( + value.to_ascii_uppercase().as_str(), + inkwell::types::StringRadix::Hexadecimal, + ) + .expect("Always valid") + .as_basic_value_enum(); + Ok(result) +} + +/// +/// Translates the block tag label push. +/// +pub fn push_tag<'ctx, D>( + context: &mut era_compiler_llvm_context::EraVMContext<'ctx, D>, + value: String, +) -> anyhow::Result> +where + D: era_compiler_llvm_context::EraVMDependency + Clone, +{ + let result = context + .field_type() + .const_int_from_string(value.as_str(), inkwell::types::StringRadix::Decimal) + .expect("Always valid"); + Ok(result.as_basic_value_enum()) +} + +/// +/// Translates the stack memory duplicate. +/// +pub fn dup<'ctx, D>( + context: &mut era_compiler_llvm_context::EraVMContext<'ctx, D>, + offset: usize, + height: usize, + original: &mut Option, +) -> anyhow::Result> +where + D: era_compiler_llvm_context::EraVMDependency + Clone, +{ + let element = &context.evmla().stack[height - offset - 1]; + let value = context.build_load( + era_compiler_llvm_context::EraVMPointer::new_stack_field( + context, + element.to_llvm().into_pointer_value(), + ), + format!("dup{offset}").as_str(), + )?; + + *original = element.original.to_owned(); + + Ok(value) +} + +/// +/// Translates the stack memory swap. +/// +pub fn swap( + context: &mut era_compiler_llvm_context::EraVMContext, + offset: usize, + height: usize, +) -> anyhow::Result<()> +where + D: era_compiler_llvm_context::EraVMDependency + Clone, +{ + let top_element = context.evmla().stack[height - 1].to_owned(); + let top_pointer = era_compiler_llvm_context::EraVMPointer::new_stack_field( + context, + top_element.to_llvm().into_pointer_value(), + ); + let top_value = context.build_load(top_pointer, format!("swap{offset}_top_value").as_str())?; + + let swap_element = context.evmla().stack[height - offset - 1].to_owned(); + let swap_pointer = era_compiler_llvm_context::EraVMPointer::new_stack_field( + context, + swap_element.to_llvm().into_pointer_value(), + ); + let swap_value = + context.build_load(swap_pointer, format!("swap{offset}_swap_value").as_str())?; + + context.evmla_mut().stack[height - 1].original = swap_element.original.to_owned(); + context.evmla_mut().stack[height - offset - 1].original = top_element.original.to_owned(); + + context.build_store(top_pointer, swap_value)?; + context.build_store(swap_pointer, top_value)?; + + Ok(()) +} + +/// +/// Translates the stack memory pop. +/// +pub fn pop(_context: &mut era_compiler_llvm_context::EraVMContext) -> anyhow::Result<()> +where + D: era_compiler_llvm_context::EraVMDependency + Clone, +{ + Ok(()) +} diff --git a/crates/solidity/src/evmla/assembly/mod.rs b/crates/solidity/src/evmla/assembly/mod.rs new file mode 100644 index 0000000..828736c --- /dev/null +++ b/crates/solidity/src/evmla/assembly/mod.rs @@ -0,0 +1,313 @@ +//! +//! The `solc --asm-json` output. +//! + +pub mod data; +pub mod instruction; + +use std::collections::BTreeMap; +use std::collections::HashSet; + +use serde::Deserialize; +use serde::Serialize; + +use crate::evmla::ethereal_ir::entry_link::EntryLink; +use crate::evmla::ethereal_ir::EtherealIR; +use crate::solc::standard_json::output::contract::evm::extra_metadata::ExtraMetadata; + +use self::data::Data; +use self::instruction::name::Name as InstructionName; +use self::instruction::Instruction; + +/// +/// The JSON assembly. +/// +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct Assembly { + /// The metadata string. + #[serde(rename = ".auxdata")] + pub auxdata: Option, + /// The deploy code instructions. + #[serde(rename = ".code")] + pub code: Option>, + /// The runtime code. + #[serde(rename = ".data")] + pub data: Option>, + + /// The full contract path. + #[serde(skip_serializing_if = "Option::is_none")] + pub full_path: Option, + /// The factory dependency paths. + #[serde(default = "HashSet::new")] + pub factory_dependencies: HashSet, + /// The EVMLA extra metadata. + #[serde(skip_serializing_if = "Option::is_none")] + pub extra_metadata: Option, +} + +impl Assembly { + /// + /// Gets the contract `keccak256` hash. + /// + pub fn keccak256(&self) -> String { + let json = serde_json::to_vec(self).expect("Always valid"); + era_compiler_llvm_context::eravm_utils::keccak256(json.as_slice()) + } + + /// + /// Sets the full contract path. + /// + pub fn set_full_path(&mut self, full_path: String) { + self.full_path = Some(full_path); + } + + /// + /// Returns the full contract path if it is set, or `` otherwise. + /// + /// # Panics + /// If the `full_path` has not been set. + /// + pub fn full_path(&self) -> &str { + self.full_path + .as_deref() + .unwrap_or_else(|| panic!("The full path of some contracts is unset")) + } + + /// + /// Get the list of missing deployable libraries. + /// + pub fn get_missing_libraries(&self) -> HashSet { + let mut missing_libraries = HashSet::new(); + if let Some(code) = self.code.as_ref() { + for instruction in code.iter() { + if let InstructionName::PUSHLIB = instruction.name { + let library_path = instruction.value.to_owned().expect("Always exists"); + missing_libraries.insert(library_path); + } + } + } + if let Some(data) = self.data.as_ref() { + for (_, data) in data.iter() { + missing_libraries.extend(data.get_missing_libraries()); + } + } + missing_libraries + } + + /// + /// Replaces the deploy code dependencies with full contract path and returns the list. + /// + pub fn deploy_dependencies_pass( + &mut self, + full_path: &str, + hash_data_mapping: &BTreeMap, + ) -> anyhow::Result> { + let mut index_path_mapping = BTreeMap::new(); + let index = "0".repeat(era_compiler_common::BYTE_LENGTH_FIELD * 2); + index_path_mapping.insert(index, full_path.to_owned()); + + let dependencies = match self.data.as_mut() { + Some(dependencies) => dependencies, + None => return Ok(index_path_mapping), + }; + for (index, data) in dependencies.iter_mut() { + if index == "0" { + continue; + } + + let mut index_extended = + "0".repeat(era_compiler_common::BYTE_LENGTH_FIELD * 2 - index.len()); + index_extended.push_str(index.as_str()); + + *data = match data { + Data::Assembly(assembly) => { + let hash = assembly.keccak256(); + let full_path = + hash_data_mapping + .get(hash.as_str()) + .cloned() + .ok_or_else(|| { + anyhow::anyhow!("Contract path not found for hash `{}`", hash) + })?; + self.factory_dependencies.insert(full_path.to_owned()); + + index_path_mapping.insert(index_extended, full_path.clone()); + Data::Path(full_path) + } + Data::Hash(hash) => { + index_path_mapping.insert(index_extended, hash.to_owned()); + continue; + } + _ => continue, + }; + } + + Ok(index_path_mapping) + } + + /// + /// Replaces the runtime code dependencies with full contract path and returns the list. + /// + pub fn runtime_dependencies_pass( + &mut self, + full_path: &str, + hash_data_mapping: &BTreeMap, + ) -> anyhow::Result> { + let mut index_path_mapping = BTreeMap::new(); + let index = "0".repeat(era_compiler_common::BYTE_LENGTH_FIELD * 2); + index_path_mapping.insert(index, full_path.to_owned()); + + let dependencies = match self + .data + .as_mut() + .and_then(|data| data.get_mut("0")) + .and_then(|data| data.get_assembly_mut()) + .and_then(|assembly| assembly.data.as_mut()) + { + Some(dependencies) => dependencies, + None => return Ok(index_path_mapping), + }; + for (index, data) in dependencies.iter_mut() { + let mut index_extended = + "0".repeat(era_compiler_common::BYTE_LENGTH_FIELD * 2 - index.len()); + index_extended.push_str(index.as_str()); + + *data = match data { + Data::Assembly(assembly) => { + let hash = assembly.keccak256(); + let full_path = + hash_data_mapping + .get(hash.as_str()) + .cloned() + .ok_or_else(|| { + anyhow::anyhow!("Contract path not found for hash `{}`", hash) + })?; + self.factory_dependencies.insert(full_path.to_owned()); + + index_path_mapping.insert(index_extended, full_path.clone()); + Data::Path(full_path) + } + Data::Hash(hash) => { + index_path_mapping.insert(index_extended, hash.to_owned()); + continue; + } + _ => continue, + }; + } + + Ok(index_path_mapping) + } +} + +impl era_compiler_llvm_context::EraVMWriteLLVM for Assembly +where + D: era_compiler_llvm_context::EraVMDependency + Clone, +{ + fn declare( + &mut self, + context: &mut era_compiler_llvm_context::EraVMContext, + ) -> anyhow::Result<()> { + let mut entry = era_compiler_llvm_context::EraVMEntryFunction::default(); + entry.declare(context)?; + + let mut runtime = era_compiler_llvm_context::EraVMRuntime::new( + era_compiler_llvm_context::EraVMAddressSpace::Heap, + ); + runtime.declare(context)?; + + era_compiler_llvm_context::EraVMDeployCodeFunction::new( + era_compiler_llvm_context::EraVMDummyLLVMWritable::default(), + ) + .declare(context)?; + era_compiler_llvm_context::EraVMRuntimeCodeFunction::new( + era_compiler_llvm_context::EraVMDummyLLVMWritable::default(), + ) + .declare(context)?; + + entry.into_llvm(context)?; + + runtime.into_llvm(context)?; + + Ok(()) + } + + fn into_llvm( + mut self, + context: &mut era_compiler_llvm_context::EraVMContext, + ) -> anyhow::Result<()> { + let full_path = self.full_path().to_owned(); + + if let Some(debug_config) = context.debug_config() { + debug_config.dump_evmla(full_path.as_str(), self.to_string().as_str())?; + } + let deploy_code_blocks = EtherealIR::get_blocks( + context.evmla().version.to_owned(), + era_compiler_llvm_context::EraVMCodeType::Deploy, + self.code + .as_deref() + .ok_or_else(|| anyhow::anyhow!("Deploy code instructions not found"))?, + )?; + + let data = self + .data + .ok_or_else(|| anyhow::anyhow!("Runtime code data not found"))? + .remove("0") + .expect("Always exists"); + if let Some(debug_config) = context.debug_config() { + debug_config.dump_evmla(full_path.as_str(), data.to_string().as_str())?; + } + let runtime_code_instructions = match data { + Data::Assembly(assembly) => assembly + .code + .ok_or_else(|| anyhow::anyhow!("Runtime code instructions not found"))?, + Data::Hash(hash) => { + anyhow::bail!("Expected runtime code instructions, found hash `{}`", hash) + } + Data::Path(path) => { + anyhow::bail!("Expected runtime code instructions, found path `{}`", path) + } + }; + let runtime_code_blocks = EtherealIR::get_blocks( + context.evmla().version.to_owned(), + era_compiler_llvm_context::EraVMCodeType::Runtime, + runtime_code_instructions.as_slice(), + )?; + + let extra_metadata = self.extra_metadata.take().unwrap_or_default(); + let mut blocks = deploy_code_blocks; + blocks.extend(runtime_code_blocks); + let mut ethereal_ir = + EtherealIR::new(context.evmla().version.to_owned(), extra_metadata, blocks)?; + if let Some(debug_config) = context.debug_config() { + debug_config.dump_ethir(full_path.as_str(), ethereal_ir.to_string().as_str())?; + } + ethereal_ir.declare(context)?; + ethereal_ir.into_llvm(context)?; + + era_compiler_llvm_context::EraVMDeployCodeFunction::new(EntryLink::new( + era_compiler_llvm_context::EraVMCodeType::Deploy, + )) + .into_llvm(context)?; + era_compiler_llvm_context::EraVMRuntimeCodeFunction::new(EntryLink::new( + era_compiler_llvm_context::EraVMCodeType::Runtime, + )) + .into_llvm(context)?; + + Ok(()) + } +} + +impl std::fmt::Display for Assembly { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if let Some(instructions) = self.code.as_ref() { + for (index, instruction) in instructions.iter().enumerate() { + match instruction.name { + InstructionName::Tag => writeln!(f, "{index:03} {instruction}")?, + _ => writeln!(f, "{index:03} {instruction}")?, + } + } + } + + Ok(()) + } +} diff --git a/crates/solidity/src/evmla/ethereal_ir/entry_link.rs b/crates/solidity/src/evmla/ethereal_ir/entry_link.rs new file mode 100644 index 0000000..8b20886 --- /dev/null +++ b/crates/solidity/src/evmla/ethereal_ir/entry_link.rs @@ -0,0 +1,58 @@ +//! +//! The Ethereal IR entry function link. +//! + +use inkwell::values::BasicValue; + +use crate::evmla::ethereal_ir::EtherealIR; + +/// +/// The Ethereal IR entry function link. +/// +/// The link represents branching between the deploy and runtime code. +/// +#[derive(Debug, Clone)] +pub struct EntryLink { + /// The code part type. + pub code_type: era_compiler_llvm_context::EraVMCodeType, +} + +impl EntryLink { + /// + /// A shortcut constructor. + /// + pub fn new(code_type: era_compiler_llvm_context::EraVMCodeType) -> Self { + Self { code_type } + } +} + +impl era_compiler_llvm_context::EraVMWriteLLVM for EntryLink +where + D: era_compiler_llvm_context::EraVMDependency + Clone, +{ + fn into_llvm( + self, + context: &mut era_compiler_llvm_context::EraVMContext, + ) -> anyhow::Result<()> { + let target = context + .get_function(EtherealIR::DEFAULT_ENTRY_FUNCTION_NAME) + .expect("Always exists") + .borrow() + .declaration(); + let is_deploy_code = match self.code_type { + era_compiler_llvm_context::EraVMCodeType::Deploy => context + .integer_type(era_compiler_common::BIT_LENGTH_BOOLEAN) + .const_int(1, false), + era_compiler_llvm_context::EraVMCodeType::Runtime => context + .integer_type(era_compiler_common::BIT_LENGTH_BOOLEAN) + .const_int(0, false), + }; + context.build_invoke( + target, + &[is_deploy_code.as_basic_value_enum()], + format!("call_link_{}", EtherealIR::DEFAULT_ENTRY_FUNCTION_NAME).as_str(), + ); + + Ok(()) + } +} diff --git a/crates/solidity/src/evmla/ethereal_ir/function/block/element/mod.rs b/crates/solidity/src/evmla/ethereal_ir/function/block/element/mod.rs new file mode 100644 index 0000000..b30e5bd --- /dev/null +++ b/crates/solidity/src/evmla/ethereal_ir/function/block/element/mod.rs @@ -0,0 +1,1372 @@ +//! +//! The Ethereal IR block element. +//! + +pub mod stack; + +use inkwell::values::BasicValue; + +use crate::evmla::assembly::instruction::codecopy; +use crate::evmla::assembly::instruction::name::Name as InstructionName; +use crate::evmla::assembly::instruction::Instruction; + +use self::stack::element::Element as StackElement; +use self::stack::Stack; + +/// +/// The Ethereal IR block element. +/// +#[derive(Debug, Clone)] +pub struct Element { + /// The Solidity compiler version. + pub solc_version: semver::Version, + /// The instruction. + pub instruction: Instruction, + /// The stack data. + pub stack: Stack, + /// The stack input. + pub stack_input: Stack, + /// The stack output. + pub stack_output: Stack, +} + +impl Element { + /// + /// A shortcut constructor. + /// + pub fn new(solc_version: semver::Version, instruction: Instruction) -> Self { + let input_size = instruction.input_size(&solc_version); + let output_size = instruction.output_size(); + + Self { + solc_version, + instruction, + stack: Stack::new(), + stack_input: Stack::with_capacity(input_size), + stack_output: Stack::with_capacity(output_size), + } + } + + /// + /// Pops the specified number of arguments, converted into their LLVM values. + /// + fn pop_arguments_llvm<'ctx, D>( + &mut self, + context: &mut era_compiler_llvm_context::EraVMContext<'ctx, D>, + ) -> Vec> + where + D: era_compiler_llvm_context::EraVMDependency + Clone, + { + let input_size = self.instruction.input_size(&context.evmla().version); + let output_size = self.instruction.output_size(); + let mut arguments = Vec::with_capacity(input_size); + for index in 0..input_size { + let pointer = context.evmla().stack + [self.stack.elements.len() + input_size - output_size - 1 - index] + .to_llvm() + .into_pointer_value(); + let value = context + .build_load( + era_compiler_llvm_context::EraVMPointer::new_stack_field(context, pointer), + format!("argument_{index}").as_str(), + ) + .unwrap(); + arguments.push(value); + } + arguments + } +} + +impl era_compiler_llvm_context::EraVMWriteLLVM for Element +where + D: era_compiler_llvm_context::EraVMDependency + Clone, +{ + fn into_llvm( + mut self, + context: &mut era_compiler_llvm_context::EraVMContext<'_, D>, + ) -> anyhow::Result<()> { + let mut original = self.instruction.value.clone(); + + let result = match self.instruction.name.clone() { + InstructionName::PUSH + | InstructionName::PUSH1 + | InstructionName::PUSH2 + | InstructionName::PUSH3 + | InstructionName::PUSH4 + | InstructionName::PUSH5 + | InstructionName::PUSH6 + | InstructionName::PUSH7 + | InstructionName::PUSH8 + | InstructionName::PUSH9 + | InstructionName::PUSH10 + | InstructionName::PUSH11 + | InstructionName::PUSH12 + | InstructionName::PUSH13 + | InstructionName::PUSH14 + | InstructionName::PUSH15 + | InstructionName::PUSH16 + | InstructionName::PUSH17 + | InstructionName::PUSH18 + | InstructionName::PUSH19 + | InstructionName::PUSH20 + | InstructionName::PUSH21 + | InstructionName::PUSH22 + | InstructionName::PUSH23 + | InstructionName::PUSH24 + | InstructionName::PUSH25 + | InstructionName::PUSH26 + | InstructionName::PUSH27 + | InstructionName::PUSH28 + | InstructionName::PUSH29 + | InstructionName::PUSH30 + | InstructionName::PUSH31 + | InstructionName::PUSH32 => crate::evmla::assembly::instruction::stack::push( + context, + self.instruction + .value + .ok_or_else(|| anyhow::anyhow!("Instruction value missing"))?, + ) + .map(Some), + InstructionName::PUSH_Tag => crate::evmla::assembly::instruction::stack::push_tag( + context, + self.instruction + .value + .ok_or_else(|| anyhow::anyhow!("Instruction value missing"))?, + ) + .map(Some), + InstructionName::PUSH_ContractHash => { + era_compiler_llvm_context::eravm_evm_create::contract_hash( + context, + self.instruction + .value + .ok_or_else(|| anyhow::anyhow!("Instruction value missing"))?, + ) + .map(|argument| Some(argument.value)) + } + InstructionName::PUSH_ContractHashSize => { + era_compiler_llvm_context::eravm_evm_create::header_size( + context, + self.instruction + .value + .ok_or_else(|| anyhow::anyhow!("Instruction value missing"))?, + ) + .map(|argument| Some(argument.value)) + } + InstructionName::PUSHLIB => { + let path = self + .instruction + .value + .ok_or_else(|| anyhow::anyhow!("Instruction value missing"))?; + + Ok(Some( + context + .resolve_library(path.as_str())? + .as_basic_value_enum(), + )) + } + InstructionName::PUSH_Data => { + let value = self + .instruction + .value + .ok_or_else(|| anyhow::anyhow!("Instruction value missing"))?; + + if value.len() > era_compiler_common::BYTE_LENGTH_FIELD * 2 { + Ok(Some(context.field_const(0).as_basic_value_enum())) + } else { + crate::evmla::assembly::instruction::stack::push(context, value).map(Some) + } + } + InstructionName::PUSHDEPLOYADDRESS => todo!(), + + InstructionName::DUP1 => crate::evmla::assembly::instruction::stack::dup( + context, + 1, + self.stack.elements.len(), + &mut original, + ) + .map(Some), + InstructionName::DUP2 => crate::evmla::assembly::instruction::stack::dup( + context, + 2, + self.stack.elements.len(), + &mut original, + ) + .map(Some), + InstructionName::DUP3 => crate::evmla::assembly::instruction::stack::dup( + context, + 3, + self.stack.elements.len(), + &mut original, + ) + .map(Some), + InstructionName::DUP4 => crate::evmla::assembly::instruction::stack::dup( + context, + 4, + self.stack.elements.len(), + &mut original, + ) + .map(Some), + InstructionName::DUP5 => crate::evmla::assembly::instruction::stack::dup( + context, + 5, + self.stack.elements.len(), + &mut original, + ) + .map(Some), + InstructionName::DUP6 => crate::evmla::assembly::instruction::stack::dup( + context, + 6, + self.stack.elements.len(), + &mut original, + ) + .map(Some), + InstructionName::DUP7 => crate::evmla::assembly::instruction::stack::dup( + context, + 7, + self.stack.elements.len(), + &mut original, + ) + .map(Some), + InstructionName::DUP8 => crate::evmla::assembly::instruction::stack::dup( + context, + 8, + self.stack.elements.len(), + &mut original, + ) + .map(Some), + InstructionName::DUP9 => crate::evmla::assembly::instruction::stack::dup( + context, + 9, + self.stack.elements.len(), + &mut original, + ) + .map(Some), + InstructionName::DUP10 => crate::evmla::assembly::instruction::stack::dup( + context, + 10, + self.stack.elements.len(), + &mut original, + ) + .map(Some), + InstructionName::DUP11 => crate::evmla::assembly::instruction::stack::dup( + context, + 11, + self.stack.elements.len(), + &mut original, + ) + .map(Some), + InstructionName::DUP12 => crate::evmla::assembly::instruction::stack::dup( + context, + 12, + self.stack.elements.len(), + &mut original, + ) + .map(Some), + InstructionName::DUP13 => crate::evmla::assembly::instruction::stack::dup( + context, + 13, + self.stack.elements.len(), + &mut original, + ) + .map(Some), + InstructionName::DUP14 => crate::evmla::assembly::instruction::stack::dup( + context, + 14, + self.stack.elements.len(), + &mut original, + ) + .map(Some), + InstructionName::DUP15 => crate::evmla::assembly::instruction::stack::dup( + context, + 15, + self.stack.elements.len(), + &mut original, + ) + .map(Some), + InstructionName::DUP16 => crate::evmla::assembly::instruction::stack::dup( + context, + 16, + self.stack.elements.len(), + &mut original, + ) + .map(Some), + + InstructionName::SWAP1 => crate::evmla::assembly::instruction::stack::swap( + context, + 1, + self.stack.elements.len(), + ) + .map(|_| None), + InstructionName::SWAP2 => crate::evmla::assembly::instruction::stack::swap( + context, + 2, + self.stack.elements.len(), + ) + .map(|_| None), + InstructionName::SWAP3 => crate::evmla::assembly::instruction::stack::swap( + context, + 3, + self.stack.elements.len(), + ) + .map(|_| None), + InstructionName::SWAP4 => crate::evmla::assembly::instruction::stack::swap( + context, + 4, + self.stack.elements.len(), + ) + .map(|_| None), + InstructionName::SWAP5 => crate::evmla::assembly::instruction::stack::swap( + context, + 5, + self.stack.elements.len(), + ) + .map(|_| None), + InstructionName::SWAP6 => crate::evmla::assembly::instruction::stack::swap( + context, + 6, + self.stack.elements.len(), + ) + .map(|_| None), + InstructionName::SWAP7 => crate::evmla::assembly::instruction::stack::swap( + context, + 7, + self.stack.elements.len(), + ) + .map(|_| None), + InstructionName::SWAP8 => crate::evmla::assembly::instruction::stack::swap( + context, + 8, + self.stack.elements.len(), + ) + .map(|_| None), + InstructionName::SWAP9 => crate::evmla::assembly::instruction::stack::swap( + context, + 9, + self.stack.elements.len(), + ) + .map(|_| None), + InstructionName::SWAP10 => crate::evmla::assembly::instruction::stack::swap( + context, + 10, + self.stack.elements.len(), + ) + .map(|_| None), + InstructionName::SWAP11 => crate::evmla::assembly::instruction::stack::swap( + context, + 11, + self.stack.elements.len(), + ) + .map(|_| None), + InstructionName::SWAP12 => crate::evmla::assembly::instruction::stack::swap( + context, + 12, + self.stack.elements.len(), + ) + .map(|_| None), + InstructionName::SWAP13 => crate::evmla::assembly::instruction::stack::swap( + context, + 13, + self.stack.elements.len(), + ) + .map(|_| None), + InstructionName::SWAP14 => crate::evmla::assembly::instruction::stack::swap( + context, + 14, + self.stack.elements.len(), + ) + .map(|_| None), + InstructionName::SWAP15 => crate::evmla::assembly::instruction::stack::swap( + context, + 15, + self.stack.elements.len(), + ) + .map(|_| None), + InstructionName::SWAP16 => crate::evmla::assembly::instruction::stack::swap( + context, + 16, + self.stack.elements.len(), + ) + .map(|_| None), + + InstructionName::POP => { + crate::evmla::assembly::instruction::stack::pop(context).map(|_| None) + } + + InstructionName::Tag => { + let destination: num::BigUint = self + .instruction + .value + .expect("Always exists") + .parse() + .expect("Always valid"); + + crate::evmla::assembly::instruction::jump::unconditional( + context, + destination, + self.stack.hash(), + ) + .map(|_| None) + } + InstructionName::JUMP => { + let destination = self.stack_input.pop_tag()?; + + crate::evmla::assembly::instruction::jump::unconditional( + context, + destination, + self.stack.hash(), + ) + .map(|_| None) + } + InstructionName::JUMPI => { + let destination = self.stack_input.pop_tag()?; + let _condition = self.stack_input.pop(); + + crate::evmla::assembly::instruction::jump::conditional( + context, + destination, + self.stack.hash(), + self.stack.elements.len(), + ) + .map(|_| None) + } + InstructionName::JUMPDEST => Ok(None), + + InstructionName::ADD => { + let arguments = self.pop_arguments_llvm(context); + era_compiler_llvm_context::eravm_evm_arithmetic::addition( + context, + arguments[0].into_int_value(), + arguments[1].into_int_value(), + ) + .map(Some) + } + InstructionName::SUB => { + let arguments = self.pop_arguments_llvm(context); + era_compiler_llvm_context::eravm_evm_arithmetic::subtraction( + context, + arguments[0].into_int_value(), + arguments[1].into_int_value(), + ) + .map(Some) + } + InstructionName::MUL => { + let arguments = self.pop_arguments_llvm(context); + era_compiler_llvm_context::eravm_evm_arithmetic::multiplication( + context, + arguments[0].into_int_value(), + arguments[1].into_int_value(), + ) + .map(Some) + } + InstructionName::DIV => { + let arguments = self.pop_arguments_llvm(context); + era_compiler_llvm_context::eravm_evm_arithmetic::division( + context, + arguments[0].into_int_value(), + arguments[1].into_int_value(), + ) + .map(Some) + } + InstructionName::MOD => { + let arguments = self.pop_arguments_llvm(context); + era_compiler_llvm_context::eravm_evm_arithmetic::remainder( + context, + arguments[0].into_int_value(), + arguments[1].into_int_value(), + ) + .map(Some) + } + InstructionName::SDIV => { + let arguments = self.pop_arguments_llvm(context); + era_compiler_llvm_context::eravm_evm_arithmetic::division_signed( + context, + arguments[0].into_int_value(), + arguments[1].into_int_value(), + ) + .map(Some) + } + InstructionName::SMOD => { + let arguments = self.pop_arguments_llvm(context); + era_compiler_llvm_context::eravm_evm_arithmetic::remainder_signed( + context, + arguments[0].into_int_value(), + arguments[1].into_int_value(), + ) + .map(Some) + } + + InstructionName::LT => { + let arguments = self.pop_arguments_llvm(context); + era_compiler_llvm_context::eravm_evm_comparison::compare( + context, + arguments[0].into_int_value(), + arguments[1].into_int_value(), + inkwell::IntPredicate::ULT, + ) + .map(Some) + } + InstructionName::GT => { + let arguments = self.pop_arguments_llvm(context); + era_compiler_llvm_context::eravm_evm_comparison::compare( + context, + arguments[0].into_int_value(), + arguments[1].into_int_value(), + inkwell::IntPredicate::UGT, + ) + .map(Some) + } + InstructionName::EQ => { + let arguments = self.pop_arguments_llvm(context); + era_compiler_llvm_context::eravm_evm_comparison::compare( + context, + arguments[0].into_int_value(), + arguments[1].into_int_value(), + inkwell::IntPredicate::EQ, + ) + .map(Some) + } + InstructionName::ISZERO => { + let arguments = self.pop_arguments_llvm(context); + era_compiler_llvm_context::eravm_evm_comparison::compare( + context, + arguments[0].into_int_value(), + context.field_const(0), + inkwell::IntPredicate::EQ, + ) + .map(Some) + } + InstructionName::SLT => { + let arguments = self.pop_arguments_llvm(context); + era_compiler_llvm_context::eravm_evm_comparison::compare( + context, + arguments[0].into_int_value(), + arguments[1].into_int_value(), + inkwell::IntPredicate::SLT, + ) + .map(Some) + } + InstructionName::SGT => { + let arguments = self.pop_arguments_llvm(context); + era_compiler_llvm_context::eravm_evm_comparison::compare( + context, + arguments[0].into_int_value(), + arguments[1].into_int_value(), + inkwell::IntPredicate::SGT, + ) + .map(Some) + } + + InstructionName::OR => { + let arguments = self.pop_arguments_llvm(context); + era_compiler_llvm_context::eravm_evm_bitwise::or( + context, + arguments[0].into_int_value(), + arguments[1].into_int_value(), + ) + .map(Some) + } + InstructionName::XOR => { + let arguments = self.pop_arguments_llvm(context); + era_compiler_llvm_context::eravm_evm_bitwise::xor( + context, + arguments[0].into_int_value(), + arguments[1].into_int_value(), + ) + .map(Some) + } + InstructionName::NOT => { + let arguments = self.pop_arguments_llvm(context); + era_compiler_llvm_context::eravm_evm_bitwise::xor( + context, + arguments[0].into_int_value(), + context.field_type().const_all_ones(), + ) + .map(Some) + } + InstructionName::AND => { + let arguments = self.pop_arguments_llvm(context); + era_compiler_llvm_context::eravm_evm_bitwise::and( + context, + arguments[0].into_int_value(), + arguments[1].into_int_value(), + ) + .map(Some) + } + InstructionName::SHL => { + let arguments = self.pop_arguments_llvm(context); + era_compiler_llvm_context::eravm_evm_bitwise::shift_left( + context, + arguments[0].into_int_value(), + arguments[1].into_int_value(), + ) + .map(Some) + } + InstructionName::SHR => { + let arguments = self.pop_arguments_llvm(context); + era_compiler_llvm_context::eravm_evm_bitwise::shift_right( + context, + arguments[0].into_int_value(), + arguments[1].into_int_value(), + ) + .map(Some) + } + InstructionName::SAR => { + let arguments = self.pop_arguments_llvm(context); + era_compiler_llvm_context::eravm_evm_bitwise::shift_right_arithmetic( + context, + arguments[0].into_int_value(), + arguments[1].into_int_value(), + ) + .map(Some) + } + InstructionName::BYTE => { + let arguments = self.pop_arguments_llvm(context); + era_compiler_llvm_context::eravm_evm_bitwise::byte( + context, + arguments[0].into_int_value(), + arguments[1].into_int_value(), + ) + .map(Some) + } + + InstructionName::ADDMOD => { + let arguments = self.pop_arguments_llvm(context); + era_compiler_llvm_context::eravm_evm_math::add_mod( + context, + arguments[0].into_int_value(), + arguments[1].into_int_value(), + arguments[2].into_int_value(), + ) + .map(Some) + } + InstructionName::MULMOD => { + let arguments = self.pop_arguments_llvm(context); + era_compiler_llvm_context::eravm_evm_math::mul_mod( + context, + arguments[0].into_int_value(), + arguments[1].into_int_value(), + arguments[2].into_int_value(), + ) + .map(Some) + } + InstructionName::EXP => { + let arguments = self.pop_arguments_llvm(context); + era_compiler_llvm_context::eravm_evm_math::exponent( + context, + arguments[0].into_int_value(), + arguments[1].into_int_value(), + ) + .map(Some) + } + InstructionName::SIGNEXTEND => { + let arguments = self.pop_arguments_llvm(context); + era_compiler_llvm_context::eravm_evm_math::sign_extend( + context, + arguments[0].into_int_value(), + arguments[1].into_int_value(), + ) + .map(Some) + } + + InstructionName::SHA3 | InstructionName::KECCAK256 => { + let arguments = self.pop_arguments_llvm(context); + era_compiler_llvm_context::eravm_evm_crypto::sha3( + context, + arguments[0].into_int_value(), + arguments[1].into_int_value(), + ) + .map(Some) + } + + InstructionName::MLOAD => { + let arguments = self.pop_arguments_llvm(context); + era_compiler_llvm_context::eravm_evm_memory::load( + context, + arguments[0].into_int_value(), + ) + .map(Some) + } + InstructionName::MSTORE => { + let arguments = self.pop_arguments_llvm(context); + era_compiler_llvm_context::eravm_evm_memory::store( + context, + arguments[0].into_int_value(), + arguments[1].into_int_value(), + ) + .map(|_| None) + } + InstructionName::MSTORE8 => { + let arguments = self.pop_arguments_llvm(context); + era_compiler_llvm_context::eravm_evm_memory::store_byte( + context, + arguments[0].into_int_value(), + arguments[1].into_int_value(), + ) + .map(|_| None) + } + InstructionName::MCOPY => { + let arguments = self.pop_arguments_llvm(context); + let destination = era_compiler_llvm_context::EraVMPointer::new_with_offset( + context, + era_compiler_llvm_context::EraVMAddressSpace::Heap, + context.byte_type(), + arguments[0].into_int_value(), + "mcopy_destination", + ); + let source = era_compiler_llvm_context::EraVMPointer::new_with_offset( + context, + era_compiler_llvm_context::EraVMAddressSpace::Heap, + context.byte_type(), + arguments[1].into_int_value(), + "mcopy_source", + ); + + context.build_memcpy( + context.intrinsics().memory_copy, + destination, + source, + arguments[2].into_int_value(), + "mcopy_size", + )?; + Ok(None) + } + + InstructionName::SLOAD => { + let arguments = self.pop_arguments_llvm(context); + era_compiler_llvm_context::eravm_evm_storage::load( + context, + arguments[0].into_int_value(), + ) + .map(Some) + } + InstructionName::SSTORE => { + let arguments = self.pop_arguments_llvm(context); + era_compiler_llvm_context::eravm_evm_storage::store( + context, + arguments[0].into_int_value(), + arguments[1].into_int_value(), + ) + .map(|_| None) + } + InstructionName::TLOAD => { + let _arguments = self.pop_arguments_llvm(context); + anyhow::bail!("The `TLOAD` instruction is not supported until zkVM v1.5.0"); + } + InstructionName::TSTORE => { + let _arguments = self.pop_arguments_llvm(context); + anyhow::bail!("The `TSTORE` instruction is not supported until zkVM v1.5.0"); + } + InstructionName::PUSHIMMUTABLE => { + let key = self + .instruction + .value + .ok_or_else(|| anyhow::anyhow!("Instruction value missing"))?; + + let offset = context + .solidity_mut() + .get_or_allocate_immutable(key.as_str()); + + let index = context.field_const(offset as u64); + era_compiler_llvm_context::eravm_evm_immutable::load(context, index).map(Some) + } + InstructionName::ASSIGNIMMUTABLE => { + let mut arguments = self.pop_arguments_llvm(context); + + let key = self + .instruction + .value + .ok_or_else(|| anyhow::anyhow!("Instruction value missing"))?; + + let offset = context.solidity_mut().allocate_immutable(key.as_str()); + + let index = context.field_const(offset as u64); + let value = arguments.pop().expect("Always exists").into_int_value(); + era_compiler_llvm_context::eravm_evm_immutable::store(context, index, value) + .map(|_| None) + } + + InstructionName::CALLDATALOAD => { + match context + .code_type() + .ok_or_else(|| anyhow::anyhow!("The contract code part type is undefined"))? + { + era_compiler_llvm_context::EraVMCodeType::Deploy => { + Ok(Some(context.field_const(0).as_basic_value_enum())) + } + era_compiler_llvm_context::EraVMCodeType::Runtime => { + let arguments = self.pop_arguments_llvm(context); + era_compiler_llvm_context::eravm_evm_calldata::load( + context, + arguments[0].into_int_value(), + ) + .map(Some) + } + } + } + InstructionName::CALLDATASIZE => { + match context + .code_type() + .ok_or_else(|| anyhow::anyhow!("The contract code part type is undefined"))? + { + era_compiler_llvm_context::EraVMCodeType::Deploy => { + Ok(Some(context.field_const(0).as_basic_value_enum())) + } + era_compiler_llvm_context::EraVMCodeType::Runtime => { + era_compiler_llvm_context::eravm_evm_calldata::size(context).map(Some) + } + } + } + InstructionName::CALLDATACOPY => { + let arguments = self.pop_arguments_llvm(context); + + match context + .code_type() + .ok_or_else(|| anyhow::anyhow!("The contract code part type is undefined"))? + { + era_compiler_llvm_context::EraVMCodeType::Deploy => { + let calldata_size = + era_compiler_llvm_context::eravm_evm_calldata::size(context)?; + + era_compiler_llvm_context::eravm_evm_calldata::copy( + context, + arguments[0].into_int_value(), + calldata_size.into_int_value(), + arguments[2].into_int_value(), + ) + .map(|_| None) + } + era_compiler_llvm_context::EraVMCodeType::Runtime => { + era_compiler_llvm_context::eravm_evm_calldata::copy( + context, + arguments[0].into_int_value(), + arguments[1].into_int_value(), + arguments[2].into_int_value(), + ) + .map(|_| None) + } + } + } + InstructionName::CODESIZE => { + match context + .code_type() + .ok_or_else(|| anyhow::anyhow!("The contract code part type is undefined"))? + { + era_compiler_llvm_context::EraVMCodeType::Deploy => { + era_compiler_llvm_context::eravm_evm_calldata::size(context).map(Some) + } + era_compiler_llvm_context::EraVMCodeType::Runtime => { + let code_source = + era_compiler_llvm_context::eravm_general::code_source(context)?; + era_compiler_llvm_context::eravm_evm_ext_code::size( + context, + code_source.into_int_value(), + ) + .map(Some) + } + } + } + InstructionName::CODECOPY => { + let arguments = self.pop_arguments_llvm(context); + + let parent = context.module().get_name().to_str().expect("Always valid"); + let source = &self.stack_input.elements[1]; + let destination = &self.stack_input.elements[2]; + + let library_marker: u64 = 0x0b; + let library_flag: u64 = 0x73; + + match (source, destination) { + (_, StackElement::Constant(destination)) + if destination == &num::BigUint::from(library_marker) => + { + codecopy::library_marker(context, library_marker, library_flag) + } + (StackElement::Data(data), _) => { + codecopy::static_data(context, arguments[0].into_int_value(), data.as_str()) + } + (StackElement::Path(source), _) if source != parent => codecopy::contract_hash( + context, + arguments[0].into_int_value(), + arguments[1].into_int_value(), + ), + _ => { + match context.code_type().ok_or_else(|| { + anyhow::anyhow!("The contract code part type is undefined") + })? { + era_compiler_llvm_context::EraVMCodeType::Deploy => { + era_compiler_llvm_context::eravm_evm_calldata::copy( + context, + arguments[0].into_int_value(), + arguments[1].into_int_value(), + arguments[2].into_int_value(), + ) + } + era_compiler_llvm_context::EraVMCodeType::Runtime => { + let calldata_size = + era_compiler_llvm_context::eravm_evm_calldata::size(context)?; + era_compiler_llvm_context::eravm_evm_calldata::copy( + context, + arguments[0].into_int_value(), + calldata_size.into_int_value(), + arguments[2].into_int_value(), + ) + } + } + } + } + .map(|_| None) + } + InstructionName::PUSHSIZE => Ok(Some(context.field_const(0).as_basic_value_enum())), + InstructionName::RETURNDATASIZE => { + era_compiler_llvm_context::eravm_evm_return_data::size(context).map(Some) + } + InstructionName::RETURNDATACOPY => { + let arguments = self.pop_arguments_llvm(context); + era_compiler_llvm_context::eravm_evm_return_data::copy( + context, + arguments[0].into_int_value(), + arguments[1].into_int_value(), + arguments[2].into_int_value(), + ) + .map(|_| None) + } + InstructionName::EXTCODESIZE => { + let arguments = self.pop_arguments_llvm(context); + era_compiler_llvm_context::eravm_evm_ext_code::size( + context, + arguments[0].into_int_value(), + ) + .map(Some) + } + InstructionName::EXTCODEHASH => { + let arguments = self.pop_arguments_llvm(context); + era_compiler_llvm_context::eravm_evm_ext_code::hash( + context, + arguments[0].into_int_value(), + ) + .map(Some) + } + + InstructionName::RETURN => { + let arguments = self.pop_arguments_llvm(context); + era_compiler_llvm_context::eravm_evm_return::r#return( + context, + arguments[0].into_int_value(), + arguments[1].into_int_value(), + ) + .map(|_| None) + } + InstructionName::REVERT => { + let arguments = self.pop_arguments_llvm(context); + era_compiler_llvm_context::eravm_evm_return::revert( + context, + arguments[0].into_int_value(), + arguments[1].into_int_value(), + ) + .map(|_| None) + } + InstructionName::STOP => { + era_compiler_llvm_context::eravm_evm_return::stop(context).map(|_| None) + } + InstructionName::INVALID => { + era_compiler_llvm_context::eravm_evm_return::invalid(context).map(|_| None) + } + + InstructionName::LOG0 => { + let mut arguments = self.pop_arguments_llvm(context); + era_compiler_llvm_context::eravm_evm_event::log( + context, + arguments.remove(0).into_int_value(), + arguments.remove(0).into_int_value(), + arguments + .into_iter() + .map(|argument| argument.into_int_value()) + .collect(), + ) + .map(|_| None) + } + InstructionName::LOG1 => { + let mut arguments = self.pop_arguments_llvm(context); + era_compiler_llvm_context::eravm_evm_event::log( + context, + arguments.remove(0).into_int_value(), + arguments.remove(0).into_int_value(), + arguments + .into_iter() + .map(|argument| argument.into_int_value()) + .collect(), + ) + .map(|_| None) + } + InstructionName::LOG2 => { + let mut arguments = self.pop_arguments_llvm(context); + era_compiler_llvm_context::eravm_evm_event::log( + context, + arguments.remove(0).into_int_value(), + arguments.remove(0).into_int_value(), + arguments + .into_iter() + .map(|argument| argument.into_int_value()) + .collect(), + ) + .map(|_| None) + } + InstructionName::LOG3 => { + let mut arguments = self.pop_arguments_llvm(context); + era_compiler_llvm_context::eravm_evm_event::log( + context, + arguments.remove(0).into_int_value(), + arguments.remove(0).into_int_value(), + arguments + .into_iter() + .map(|argument| argument.into_int_value()) + .collect(), + ) + .map(|_| None) + } + InstructionName::LOG4 => { + let mut arguments = self.pop_arguments_llvm(context); + era_compiler_llvm_context::eravm_evm_event::log( + context, + arguments.remove(0).into_int_value(), + arguments.remove(0).into_int_value(), + arguments + .into_iter() + .map(|argument| argument.into_int_value()) + .collect(), + ) + .map(|_| None) + } + + InstructionName::CALL => { + let mut arguments = self.pop_arguments_llvm(context); + + let gas = arguments.remove(0).into_int_value(); + let address = arguments.remove(0).into_int_value(); + let value = arguments.remove(0).into_int_value(); + let input_offset = arguments.remove(0).into_int_value(); + let input_size = arguments.remove(0).into_int_value(); + let output_offset = arguments.remove(0).into_int_value(); + let output_size = arguments.remove(0).into_int_value(); + + todo!() + /* + era_compiler_llvm_context::eravm_evm_call::default( + context, + context.llvm_runtime().far_call, + gas, + address, + Some(value), + input_offset, + input_size, + output_offset, + output_size, + vec![], + ) + .map(Some) + */ + } + InstructionName::STATICCALL => { + let mut arguments = self.pop_arguments_llvm(context); + + let gas = arguments.remove(0).into_int_value(); + let address = arguments.remove(0).into_int_value(); + let input_offset = arguments.remove(0).into_int_value(); + let input_size = arguments.remove(0).into_int_value(); + let output_offset = arguments.remove(0).into_int_value(); + let output_size = arguments.remove(0).into_int_value(); + + era_compiler_llvm_context::eravm_evm_call::default( + context, + context.llvm_runtime().static_call, + gas, + address, + None, + input_offset, + input_size, + output_offset, + output_size, + vec![], + ) + .map(Some) + } + InstructionName::DELEGATECALL => { + let mut arguments = self.pop_arguments_llvm(context); + + let gas = arguments.remove(0).into_int_value(); + let address = arguments.remove(0).into_int_value(); + let input_offset = arguments.remove(0).into_int_value(); + let input_size = arguments.remove(0).into_int_value(); + let output_offset = arguments.remove(0).into_int_value(); + let output_size = arguments.remove(0).into_int_value(); + + era_compiler_llvm_context::eravm_evm_call::default( + context, + context.llvm_runtime().delegate_call, + gas, + address, + None, + input_offset, + input_size, + output_offset, + output_size, + vec![], + ) + .map(Some) + } + + InstructionName::CREATE | InstructionName::ZK_CREATE => { + let arguments = self.pop_arguments_llvm(context); + + let value = arguments[0].into_int_value(); + let input_offset = arguments[1].into_int_value(); + let input_length = arguments[2].into_int_value(); + + era_compiler_llvm_context::eravm_evm_create::create( + context, + value, + input_offset, + input_length, + ) + .map(Some) + } + InstructionName::CREATE2 | InstructionName::ZK_CREATE2 => { + let arguments = self.pop_arguments_llvm(context); + + let value = arguments[0].into_int_value(); + let input_offset = arguments[1].into_int_value(); + let input_length = arguments[2].into_int_value(); + let salt = arguments[3].into_int_value(); + + era_compiler_llvm_context::eravm_evm_create::create2( + context, + value, + input_offset, + input_length, + Some(salt), + ) + .map(Some) + } + + InstructionName::ADDRESS => todo!(), + InstructionName::CALLER => todo!(), + + InstructionName::CALLVALUE => { + era_compiler_llvm_context::eravm_evm_ether_gas::value(context).map(Some) + } + InstructionName::GAS => { + era_compiler_llvm_context::eravm_evm_ether_gas::gas(context).map(Some) + } + InstructionName::BALANCE => { + let arguments = self.pop_arguments_llvm(context); + + let address = arguments[0].into_int_value(); + era_compiler_llvm_context::eravm_evm_ether_gas::balance(context, address).map(Some) + } + InstructionName::SELFBALANCE => todo!(), + + InstructionName::GASLIMIT => { + era_compiler_llvm_context::eravm_evm_contract_context::gas_limit(context).map(Some) + } + InstructionName::GASPRICE => { + era_compiler_llvm_context::eravm_evm_contract_context::gas_price(context).map(Some) + } + InstructionName::ORIGIN => { + era_compiler_llvm_context::eravm_evm_contract_context::origin(context).map(Some) + } + InstructionName::CHAINID => { + era_compiler_llvm_context::eravm_evm_contract_context::chain_id(context).map(Some) + } + InstructionName::TIMESTAMP => { + era_compiler_llvm_context::eravm_evm_contract_context::block_timestamp(context) + .map(Some) + } + InstructionName::NUMBER => { + era_compiler_llvm_context::eravm_evm_contract_context::block_number(context) + .map(Some) + } + InstructionName::BLOCKHASH => { + let arguments = self.pop_arguments_llvm(context); + let index = arguments[0].into_int_value(); + + era_compiler_llvm_context::eravm_evm_contract_context::block_hash(context, index) + .map(Some) + } + InstructionName::BLOBHASH => { + let _arguments = self.pop_arguments_llvm(context); + anyhow::bail!("The `BLOBHASH` instruction is not supported until zkVM v1.5.0"); + } + InstructionName::DIFFICULTY | InstructionName::PREVRANDAO => { + era_compiler_llvm_context::eravm_evm_contract_context::difficulty(context).map(Some) + } + InstructionName::COINBASE => { + era_compiler_llvm_context::eravm_evm_contract_context::coinbase(context).map(Some) + } + InstructionName::BASEFEE => { + era_compiler_llvm_context::eravm_evm_contract_context::basefee(context).map(Some) + } + InstructionName::BLOBBASEFEE => { + anyhow::bail!("The `BLOBBASEFEE` instruction is not supported until zkVM v1.5.0"); + } + InstructionName::MSIZE => { + era_compiler_llvm_context::eravm_evm_contract_context::msize(context).map(Some) + } + + InstructionName::CALLCODE => { + let mut _arguments = self.pop_arguments_llvm(context); + anyhow::bail!("The `CALLCODE` instruction is not supported"); + } + InstructionName::PC => { + anyhow::bail!("The `PC` instruction is not supported"); + } + InstructionName::EXTCODECOPY => { + let _arguments = self.pop_arguments_llvm(context); + anyhow::bail!("The `EXTCODECOPY` instruction is not supported"); + } + InstructionName::SELFDESTRUCT => { + let _arguments = self.pop_arguments_llvm(context); + anyhow::bail!("The `SELFDESTRUCT` instruction is not supported"); + } + + InstructionName::RecursiveCall { + name, + entry_key, + stack_hash, + output_size, + return_address, + .. + } => { + let mut arguments = self.pop_arguments_llvm(context); + arguments.pop(); + arguments.reverse(); + arguments.pop(); + + let function = context + .get_function(format!("{name}_{entry_key}").as_str()) + .expect("Always exists") + .borrow() + .declaration(); + let result = context.build_call( + function, + arguments.as_slice(), + format!("call_{}", name).as_str(), + ); + match result { + Some(value) if value.is_int_value() => { + let pointer = context.evmla().stack + [self.stack.elements.len() - output_size] + .to_llvm() + .into_pointer_value(); + context.build_store( + era_compiler_llvm_context::EraVMPointer::new_stack_field( + context, pointer, + ), + value, + )?; + } + Some(value) if value.is_struct_value() => { + let return_value = value.into_struct_value(); + for index in 0..output_size { + let value = context + .builder() + .build_extract_value( + return_value, + index as u32, + format!("return_value_element_{}", index).as_str(), + ) + .expect("Always exists"); + let pointer = era_compiler_llvm_context::EraVMPointer::new( + context.field_type(), + era_compiler_llvm_context::EraVMAddressSpace::Stack, + context.evmla().stack + [self.stack.elements.len() - output_size + index] + .to_llvm() + .into_pointer_value(), + ); + context.build_store(pointer, value)?; + } + } + Some(_) => { + panic!("Only integers and structures can be returned from Ethir functions") + } + None => {} + } + + let return_block = context + .current_function() + .borrow() + .evmla() + .find_block(&return_address, &stack_hash)?; + context.build_unconditional_branch(return_block.inner()); + return Ok(()); + } + InstructionName::RecursiveReturn { .. } => { + let mut arguments = self.pop_arguments_llvm(context); + arguments.reverse(); + arguments.pop(); + + match context.current_function().borrow().r#return() { + era_compiler_llvm_context::EraVMFunctionReturn::None => {} + era_compiler_llvm_context::EraVMFunctionReturn::Primitive { pointer } => { + assert_eq!(arguments.len(), 1); + context.build_store(pointer, arguments.remove(0))?; + } + era_compiler_llvm_context::EraVMFunctionReturn::Compound { + pointer, .. + } => { + for (index, argument) in arguments.into_iter().enumerate() { + let element_pointer = context.build_gep( + pointer, + &[ + context.field_const(0), + context.integer_const( + era_compiler_common::BIT_LENGTH_X32, + index as u64, + ), + ], + context.field_type(), + format!("return_value_pointer_element_{}", index).as_str(), + ); + context.build_store(element_pointer, argument)?; + } + } + } + + let return_block = context.current_function().borrow().return_block(); + context.build_unconditional_branch(return_block); + Ok(None) + } + }?; + + if let Some(result) = result { + let pointer = context.evmla().stack[self.stack.elements.len() - 1] + .to_llvm() + .into_pointer_value(); + context.build_store( + era_compiler_llvm_context::EraVMPointer::new_stack_field(context, pointer), + result, + )?; + context.evmla_mut().stack[self.stack.elements.len() - 1].original = original; + } + + Ok(()) + } +} + +impl std::fmt::Display for Element { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut stack = self.stack.to_owned(); + for _ in 0..self.stack_output.len() { + let _ = stack.pop(); + } + + write!(f, "{:80}{}", self.instruction.to_string(), stack)?; + if !self.stack_input.is_empty() { + write!(f, " - {}", self.stack_input)?; + } + if !self.stack_output.is_empty() { + write!(f, " + {}", self.stack_output)?; + } + Ok(()) + } +} diff --git a/crates/solidity/src/evmla/ethereal_ir/function/block/element/stack/element.rs b/crates/solidity/src/evmla/ethereal_ir/function/block/element/stack/element.rs new file mode 100644 index 0000000..5893f60 --- /dev/null +++ b/crates/solidity/src/evmla/ethereal_ir/function/block/element/stack/element.rs @@ -0,0 +1,41 @@ +//! +//! The Ethereal IR block element stack element. +//! + +/// +/// The Ethereal IR block element stack element. +/// +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Element { + /// The runtime value. + Value(String), + /// The compile-time value. + Constant(num::BigUint), + /// The compile-time destination tag. + Tag(num::BigUint), + /// The compile-time path. + Path(String), + /// The compile-time hexadecimal data chunk. + Data(String), + /// The recursive function return address. + ReturnAddress(usize), +} + +impl Element { + pub fn value(identifier: String) -> Self { + Self::Value(identifier) + } +} + +impl std::fmt::Display for Element { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Value(identifier) => write!(f, "V_{identifier}"), + Self::Constant(value) => write!(f, "{value:X}"), + Self::Tag(tag) => write!(f, "T_{tag}"), + Self::Path(path) => write!(f, "{path}"), + Self::Data(data) => write!(f, "{data}"), + Self::ReturnAddress(_) => write!(f, "RETURN_ADDRESS"), + } + } +} diff --git a/crates/solidity/src/evmla/ethereal_ir/function/block/element/stack/mod.rs b/crates/solidity/src/evmla/ethereal_ir/function/block/element/stack/mod.rs new file mode 100644 index 0000000..77954fc --- /dev/null +++ b/crates/solidity/src/evmla/ethereal_ir/function/block/element/stack/mod.rs @@ -0,0 +1,149 @@ +//! +//! The Ethereal IR block element stack. +//! + +pub mod element; + +use self::element::Element; + +/// +/// The Ethereal IR block element stack. +/// +#[derive(Debug, Default, Clone)] +pub struct Stack { + /// The stack elements. + pub elements: Vec, +} + +impl Stack { + /// The default stack size. + pub const DEFAULT_STACK_SIZE: usize = 16; + + /// + /// A shortcut constructor. + /// + pub fn new() -> Self { + Self { + elements: Vec::with_capacity(Self::DEFAULT_STACK_SIZE), + } + } + + /// + /// A shortcut constructor. + /// + pub fn with_capacity(capacity: usize) -> Self { + Self { + elements: Vec::with_capacity(capacity), + } + } + + /// + /// A shortcut constructor. + /// + pub fn new_with_elements(elements: Vec) -> Self { + Self { elements } + } + + /// + /// The stack state hash, which acts as a block identifier. + /// + /// Each block clone has its own initial stack state, which uniquely identifies the block. + /// + pub fn hash(&self) -> md5::Digest { + let mut hash_context = md5::Context::new(); + for element in self.elements.iter() { + match element { + Element::Tag(tag) => hash_context.consume(tag.to_bytes_be()), + _ => hash_context.consume([0]), + } + } + hash_context.compute() + } + + /// + /// Pushes an element onto the stack. + /// + pub fn push(&mut self, element: Element) { + self.elements.push(element); + } + + /// + /// Appends another stack on top of this one. + /// + pub fn append(&mut self, other: &mut Self) { + self.elements.append(&mut other.elements); + } + + /// + /// Pops a stack element. + /// + pub fn pop(&mut self) -> anyhow::Result { + self.elements + .pop() + .ok_or_else(|| anyhow::anyhow!("Stack underflow")) + } + + /// + /// Pops the tag from the top. + /// + pub fn pop_tag(&mut self) -> anyhow::Result { + match self.elements.pop() { + Some(Element::Tag(tag)) => Ok(tag), + Some(element) => anyhow::bail!("Expected tag, found {}", element), + None => anyhow::bail!("Stack underflow"), + } + } + + /// + /// Swaps two stack elements. + /// + pub fn swap(&mut self, index: usize) -> anyhow::Result<()> { + if self.elements.len() < index + 1 { + anyhow::bail!("Stack underflow"); + } + + let length = self.elements.len(); + self.elements.swap(length - 1, length - 1 - index); + + Ok(()) + } + + /// + /// Duplicates a stack element. + /// + pub fn dup(&mut self, index: usize) -> anyhow::Result { + if self.elements.len() < index { + anyhow::bail!("Stack underflow"); + } + + Ok(self.elements[self.elements.len() - index].to_owned()) + } + + /// + /// Returns the stack length. + /// + pub fn len(&self) -> usize { + self.elements.len() + } + + /// + /// Returns an emptiness flag. + /// + pub fn is_empty(&self) -> bool { + self.elements.len() == 0 + } +} + +impl std::fmt::Display for Stack { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "[ {} ]", + self.elements + .iter() + .map(Element::to_string) + .collect::>() + .join(" | ") + ) + } +} diff --git a/crates/solidity/src/evmla/ethereal_ir/function/block/mod.rs b/crates/solidity/src/evmla/ethereal_ir/function/block/mod.rs new file mode 100644 index 0000000..60ffcc4 --- /dev/null +++ b/crates/solidity/src/evmla/ethereal_ir/function/block/mod.rs @@ -0,0 +1,167 @@ +//! +//! The Ethereal IR block. +//! + +pub mod element; + +use std::collections::HashSet; + +use num::Zero; + +use crate::evmla::assembly::instruction::name::Name as InstructionName; +use crate::evmla::assembly::instruction::Instruction; + +use self::element::stack::Stack as ElementStack; +use self::element::Element; + +/// +/// The Ethereal IR block. +/// +#[derive(Debug, Clone)] +pub struct Block { + /// The Solidity compiler version. + pub solc_version: semver::Version, + /// The block key. + pub key: era_compiler_llvm_context::EraVMFunctionBlockKey, + /// The block instance. + pub instance: Option, + /// The block elements relevant to the stack consistency. + pub elements: Vec, + /// The block predecessors. + pub predecessors: HashSet<(era_compiler_llvm_context::EraVMFunctionBlockKey, usize)>, + /// The initial stack state. + pub initial_stack: ElementStack, + /// The stack. + pub stack: ElementStack, + /// The extra block hashes for alternative routes. + pub extra_hashes: Vec, +} + +impl Block { + /// The elements vector initial capacity. + pub const ELEMENTS_VECTOR_DEFAULT_CAPACITY: usize = 64; + /// The predecessors hashset initial capacity. + pub const PREDECESSORS_HASHSET_DEFAULT_CAPACITY: usize = 4; + + /// + /// Assembles a block from the sequence of instructions. + /// + pub fn try_from_instructions( + solc_version: semver::Version, + code_type: era_compiler_llvm_context::EraVMCodeType, + slice: &[Instruction], + ) -> anyhow::Result<(Self, usize)> { + let mut cursor = 0; + + let tag: num::BigUint = match slice[cursor].name { + InstructionName::Tag => { + let tag = slice[cursor] + .value + .as_deref() + .expect("Always exists") + .parse() + .expect("Always valid"); + cursor += 1; + tag + } + _ => num::BigUint::zero(), + }; + + let mut block = Self { + solc_version: solc_version.clone(), + key: era_compiler_llvm_context::EraVMFunctionBlockKey::new(code_type, tag), + instance: None, + elements: Vec::with_capacity(Self::ELEMENTS_VECTOR_DEFAULT_CAPACITY), + predecessors: HashSet::with_capacity(Self::PREDECESSORS_HASHSET_DEFAULT_CAPACITY), + initial_stack: ElementStack::new(), + stack: ElementStack::new(), + extra_hashes: vec![], + }; + + let mut dead_code = false; + while cursor < slice.len() { + if !dead_code { + let element: Element = Element::new(solc_version.clone(), slice[cursor].to_owned()); + block.elements.push(element); + } + + match slice[cursor].name { + InstructionName::RETURN + | InstructionName::REVERT + | InstructionName::STOP + | InstructionName::INVALID => { + cursor += 1; + dead_code = true; + } + InstructionName::JUMP => { + cursor += 1; + dead_code = true; + } + InstructionName::Tag => { + break; + } + _ => { + cursor += 1; + } + } + } + + Ok((block, cursor)) + } + + /// + /// Inserts a predecessor tag. + /// + pub fn insert_predecessor( + &mut self, + key: era_compiler_llvm_context::EraVMFunctionBlockKey, + instance: usize, + ) { + self.predecessors.insert((key, instance)); + } +} + +impl era_compiler_llvm_context::EraVMWriteLLVM for Block +where + D: era_compiler_llvm_context::EraVMDependency + Clone, +{ + fn into_llvm( + self, + context: &mut era_compiler_llvm_context::EraVMContext, + ) -> anyhow::Result<()> { + context.set_code_type(self.key.code_type); + + for element in self.elements.into_iter() { + element.into_llvm(context)?; + } + + Ok(()) + } +} + +impl std::fmt::Display for Block { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + writeln!( + f, + "block_{}/{}: {}", + self.key, + self.instance.unwrap_or_default(), + if self.predecessors.is_empty() { + "".to_owned() + } else { + format!( + "(predecessors: {})", + self.predecessors + .iter() + .map(|(key, instance)| format!("{}/{}", key, instance)) + .collect::>() + .join(", ") + ) + }, + )?; + for element in self.elements.iter() { + writeln!(f, " {element}")?; + } + Ok(()) + } +} diff --git a/crates/solidity/src/evmla/ethereal_ir/function/mod.rs b/crates/solidity/src/evmla/ethereal_ir/function/mod.rs new file mode 100644 index 0000000..d08aaad --- /dev/null +++ b/crates/solidity/src/evmla/ethereal_ir/function/mod.rs @@ -0,0 +1,1364 @@ +//! +//! The Ethereal IR function. +//! + +pub mod block; +pub mod queue_element; +pub mod r#type; +pub mod visited_element; + +use std::collections::BTreeMap; +use std::collections::BTreeSet; +use std::collections::HashMap; + +use inkwell::types::BasicType; +use inkwell::values::BasicValue; +use num::CheckedAdd; +use num::CheckedDiv; +use num::CheckedMul; +use num::CheckedSub; +use num::Num; +use num::One; +use num::ToPrimitive; +use num::Zero; + +use crate::evmla::assembly::instruction::name::Name as InstructionName; +use crate::evmla::assembly::instruction::Instruction; +use crate::evmla::ethereal_ir::function::block::element::stack::element::Element; +use crate::evmla::ethereal_ir::function::block::element::stack::Stack; +use crate::evmla::ethereal_ir::EtherealIR; +use crate::solc::standard_json::output::contract::evm::extra_metadata::recursive_function::RecursiveFunction; +use crate::solc::standard_json::output::contract::evm::extra_metadata::ExtraMetadata; + +use self::block::element::stack::element::Element as StackElement; +use self::block::element::Element as BlockElement; +use self::block::Block; +use self::queue_element::QueueElement; +use self::r#type::Type; +use self::visited_element::VisitedElement; + +/// +/// The Ethereal IR function. +/// +#[derive(Debug, Clone)] +pub struct Function { + /// The Solidity compiler version. + pub solc_version: semver::Version, + /// The function name. + pub name: String, + /// The separately labelled blocks. + pub blocks: BTreeMap>, + /// The function type. + pub r#type: Type, + /// The function stack size. + pub stack_size: usize, +} + +impl Function { + /// + /// A shortcut constructor. + /// + pub fn new(solc_version: semver::Version, r#type: Type) -> Self { + let name = match r#type { + Type::Initial => EtherealIR::DEFAULT_ENTRY_FUNCTION_NAME.to_string(), + Type::Recursive { + ref name, + ref block_key, + .. + } => format!("{name}_{block_key}"), + }; + + Self { + solc_version, + name, + blocks: BTreeMap::new(), + r#type, + stack_size: 0, + } + } + + /// + /// Runs the function block traversal. + /// + pub fn traverse( + &mut self, + blocks: &HashMap, + functions: &mut BTreeMap, + extra_metadata: &ExtraMetadata, + visited_functions: &mut BTreeSet, + ) -> anyhow::Result<()> { + let mut visited_blocks = BTreeSet::new(); + + match self.r#type { + Type::Initial => { + for code_type in [ + era_compiler_llvm_context::EraVMCodeType::Deploy, + era_compiler_llvm_context::EraVMCodeType::Runtime, + ] { + self.consume_block( + blocks, + functions, + extra_metadata, + visited_functions, + &mut visited_blocks, + QueueElement::new( + era_compiler_llvm_context::EraVMFunctionBlockKey::new( + code_type, + num::BigUint::zero(), + ), + None, + Stack::new(), + ), + )?; + } + } + Type::Recursive { + ref block_key, + input_size, + output_size, + .. + } => { + let mut stack = Stack::with_capacity(1 + input_size); + stack.push(Element::ReturnAddress(output_size)); + stack.append(&mut Stack::new_with_elements(vec![ + Element::value( + "ARGUMENT".to_owned() + ); + input_size + ])); + + self.consume_block( + blocks, + functions, + extra_metadata, + visited_functions, + &mut visited_blocks, + QueueElement::new(block_key.to_owned(), None, stack), + )?; + } + } + + self.finalize(); + + Ok(()) + } + + /// + /// Consumes the entry or a conditional block attached to another one. + /// + fn consume_block( + &mut self, + blocks: &HashMap, + functions: &mut BTreeMap, + extra_metadata: &ExtraMetadata, + visited_functions: &mut BTreeSet, + visited_blocks: &mut BTreeSet, + mut queue_element: QueueElement, + ) -> anyhow::Result<()> { + let version = self.solc_version.to_owned(); + + let mut queue = vec![]; + + let mut block = blocks + .get(&queue_element.block_key) + .cloned() + .ok_or_else(|| { + anyhow::anyhow!("Undeclared destination block {}", queue_element.block_key) + })?; + block.initial_stack = queue_element.stack.clone(); + let block = self.insert_block(block); + block.stack = block.initial_stack.clone(); + if let Some(predecessor) = queue_element.predecessor.take() { + block.insert_predecessor(predecessor.0, predecessor.1); + } + + let visited_element = + VisitedElement::new(queue_element.block_key.clone(), queue_element.stack.hash()); + if visited_blocks.contains(&visited_element) { + return Ok(()); + } + visited_blocks.insert(visited_element); + + let mut block_size = 0; + for block_element in block.elements.iter_mut() { + block_size += 1; + + if Self::handle_instruction( + blocks, + functions, + extra_metadata, + visited_functions, + block.key.code_type, + block.instance.unwrap_or_default(), + &mut block.stack, + block_element, + &version, + &mut queue, + &mut queue_element, + ) + .is_err() + { + block_element.instruction = Instruction::invalid(&block_element.instruction); + block_element.stack = block.stack.clone(); + break; + } + } + block.elements.truncate(block_size); + + for element in queue.into_iter() { + self.consume_block( + blocks, + functions, + extra_metadata, + visited_functions, + visited_blocks, + element, + )?; + } + + Ok(()) + } + + /// + /// Processes an instruction, returning an error, if there is an invalid stack state. + /// + /// The blocks with an invalid stack state are considered being partially unreachable, and + /// the invalid part is truncated after terminating with an `INVALID` instruction. + /// + #[allow(clippy::too_many_arguments)] + fn handle_instruction( + blocks: &HashMap, + functions: &mut BTreeMap, + extra_metadata: &ExtraMetadata, + visited_functions: &mut BTreeSet, + code_type: era_compiler_llvm_context::EraVMCodeType, + instance: usize, + block_stack: &mut Stack, + block_element: &mut BlockElement, + version: &semver::Version, + queue: &mut Vec, + queue_element: &mut QueueElement, + ) -> anyhow::Result<()> { + let (stack_output, queue_element) = match block_element.instruction { + Instruction { + name: InstructionName::PUSH_Tag, + value: Some(ref tag), + .. + } => { + let tag: num::BigUint = tag.parse().expect("Always valid"); + (vec![Element::Tag(tag & num::BigUint::from(u64::MAX))], None) + } + ref instruction @ Instruction { + name: InstructionName::JUMP, + .. + } => { + queue_element.predecessor = Some((queue_element.block_key.clone(), instance)); + + let block_key = match block_stack + .elements + .last() + .ok_or_else(|| anyhow::anyhow!("Destination tag is missing"))? + { + Element::Tag(destination) if destination > &num::BigUint::from(u32::MAX) => { + era_compiler_llvm_context::EraVMFunctionBlockKey::new( + era_compiler_llvm_context::EraVMCodeType::Runtime, + destination.to_owned() - num::BigUint::from(1u64 << 32), + ) + } + Element::Tag(destination) => { + era_compiler_llvm_context::EraVMFunctionBlockKey::new( + code_type, + destination.to_owned(), + ) + } + Element::ReturnAddress(output_size) => { + block_element.instruction = + Instruction::recursive_return(1 + output_size, instruction); + Self::update_io_data(block_stack, block_element, 1 + output_size, vec![])?; + return Ok(()); + } + element => { + return Err(anyhow::anyhow!( + "The {} instruction expected a tag or return address, found {}", + instruction.name, + element + )); + } + }; + + let (next_block_key, stack_output) = + if let Some(recursive_function) = extra_metadata.get(&block_key) { + Self::handle_recursive_function_call( + recursive_function, + blocks, + functions, + extra_metadata, + visited_functions, + block_key, + block_stack, + block_element, + version, + )? + } else { + (block_key, vec![]) + }; + + ( + stack_output, + Some(QueueElement::new( + next_block_key, + queue_element.predecessor.clone(), + Stack::new(), + )), + ) + } + ref instruction @ Instruction { + name: InstructionName::JUMPI, + .. + } => { + queue_element.predecessor = Some((queue_element.block_key.clone(), instance)); + + let block_key = match block_stack + .elements + .last() + .ok_or_else(|| anyhow::anyhow!("Destination tag is missing"))? + { + Element::Tag(destination) if destination > &num::BigUint::from(u32::MAX) => { + era_compiler_llvm_context::EraVMFunctionBlockKey::new( + era_compiler_llvm_context::EraVMCodeType::Runtime, + destination.to_owned() - num::BigUint::from(1u64 << 32), + ) + } + Element::Tag(destination) => { + era_compiler_llvm_context::EraVMFunctionBlockKey::new( + code_type, + destination.to_owned(), + ) + } + element => { + return Err(anyhow::anyhow!( + "The {} instruction expected a tag or return address, found {}", + instruction.name, + element + )); + } + }; + + ( + vec![], + Some(QueueElement::new( + block_key, + queue_element.predecessor.clone(), + Stack::new(), + )), + ) + } + Instruction { + name: InstructionName::Tag, + value: Some(ref tag), + .. + } => { + let tag: num::BigUint = tag.parse().expect("Always valid"); + let block_key = + era_compiler_llvm_context::EraVMFunctionBlockKey::new(code_type, tag); + + queue_element.predecessor = Some((queue_element.block_key.clone(), instance)); + queue_element.block_key = block_key.clone(); + + ( + vec![], + Some(QueueElement::new( + block_key, + queue_element.predecessor.clone(), + Stack::new(), + )), + ) + } + + Instruction { + name: InstructionName::SWAP1, + .. + } => { + block_stack.swap(1)?; + (vec![], None) + } + Instruction { + name: InstructionName::SWAP2, + .. + } => { + block_stack.swap(2)?; + (vec![], None) + } + Instruction { + name: InstructionName::SWAP3, + .. + } => { + block_stack.swap(3)?; + (vec![], None) + } + Instruction { + name: InstructionName::SWAP4, + .. + } => { + block_stack.swap(4)?; + (vec![], None) + } + Instruction { + name: InstructionName::SWAP5, + .. + } => { + block_stack.swap(5)?; + (vec![], None) + } + Instruction { + name: InstructionName::SWAP6, + .. + } => { + block_stack.swap(6)?; + (vec![], None) + } + Instruction { + name: InstructionName::SWAP7, + .. + } => { + block_stack.swap(7)?; + (vec![], None) + } + Instruction { + name: InstructionName::SWAP8, + .. + } => { + block_stack.swap(8)?; + (vec![], None) + } + Instruction { + name: InstructionName::SWAP9, + .. + } => { + block_stack.swap(9)?; + (vec![], None) + } + Instruction { + name: InstructionName::SWAP10, + .. + } => { + block_stack.swap(10)?; + (vec![], None) + } + Instruction { + name: InstructionName::SWAP11, + .. + } => { + block_stack.swap(11)?; + (vec![], None) + } + Instruction { + name: InstructionName::SWAP12, + .. + } => { + block_stack.swap(12)?; + (vec![], None) + } + Instruction { + name: InstructionName::SWAP13, + .. + } => { + block_stack.swap(13)?; + (vec![], None) + } + Instruction { + name: InstructionName::SWAP14, + .. + } => { + block_stack.swap(14)?; + (vec![], None) + } + Instruction { + name: InstructionName::SWAP15, + .. + } => { + block_stack.swap(15)?; + (vec![], None) + } + Instruction { + name: InstructionName::SWAP16, + .. + } => { + block_stack.swap(16)?; + (vec![], None) + } + + Instruction { + name: InstructionName::DUP1, + .. + } => (vec![block_stack.dup(1)?], None), + Instruction { + name: InstructionName::DUP2, + .. + } => (vec![block_stack.dup(2)?], None), + Instruction { + name: InstructionName::DUP3, + .. + } => (vec![block_stack.dup(3)?], None), + Instruction { + name: InstructionName::DUP4, + .. + } => (vec![block_stack.dup(4)?], None), + Instruction { + name: InstructionName::DUP5, + .. + } => (vec![block_stack.dup(5)?], None), + Instruction { + name: InstructionName::DUP6, + .. + } => (vec![block_stack.dup(6)?], None), + Instruction { + name: InstructionName::DUP7, + .. + } => (vec![block_stack.dup(7)?], None), + Instruction { + name: InstructionName::DUP8, + .. + } => (vec![block_stack.dup(8)?], None), + Instruction { + name: InstructionName::DUP9, + .. + } => (vec![block_stack.dup(9)?], None), + Instruction { + name: InstructionName::DUP10, + .. + } => (vec![block_stack.dup(10)?], None), + Instruction { + name: InstructionName::DUP11, + .. + } => (vec![block_stack.dup(11)?], None), + Instruction { + name: InstructionName::DUP12, + .. + } => (vec![block_stack.dup(12)?], None), + Instruction { + name: InstructionName::DUP13, + .. + } => (vec![block_stack.dup(13)?], None), + Instruction { + name: InstructionName::DUP14, + .. + } => (vec![block_stack.dup(14)?], None), + Instruction { + name: InstructionName::DUP15, + .. + } => (vec![block_stack.dup(15)?], None), + Instruction { + name: InstructionName::DUP16, + .. + } => (vec![block_stack.dup(16)?], None), + + Instruction { + name: + InstructionName::PUSH + | InstructionName::PUSH1 + | InstructionName::PUSH2 + | InstructionName::PUSH3 + | InstructionName::PUSH4 + | InstructionName::PUSH5 + | InstructionName::PUSH6 + | InstructionName::PUSH7 + | InstructionName::PUSH8 + | InstructionName::PUSH9 + | InstructionName::PUSH10 + | InstructionName::PUSH11 + | InstructionName::PUSH12 + | InstructionName::PUSH13 + | InstructionName::PUSH14 + | InstructionName::PUSH15 + | InstructionName::PUSH16 + | InstructionName::PUSH17 + | InstructionName::PUSH18 + | InstructionName::PUSH19 + | InstructionName::PUSH20 + | InstructionName::PUSH21 + | InstructionName::PUSH22 + | InstructionName::PUSH23 + | InstructionName::PUSH24 + | InstructionName::PUSH25 + | InstructionName::PUSH26 + | InstructionName::PUSH27 + | InstructionName::PUSH28 + | InstructionName::PUSH29 + | InstructionName::PUSH30 + | InstructionName::PUSH31 + | InstructionName::PUSH32, + value: Some(ref constant), + .. + } => ( + vec![num::BigUint::from_str_radix( + constant.as_str(), + era_compiler_common::BASE_HEXADECIMAL, + ) + .map(StackElement::Constant)?], + None, + ), + Instruction { + name: + InstructionName::PUSH_ContractHash + | InstructionName::PUSH_ContractHashSize + | InstructionName::PUSHLIB, + value: Some(ref path), + .. + } => (vec![StackElement::Path(path.to_owned())], None), + Instruction { + name: InstructionName::PUSH_Data, + value: Some(ref data), + .. + } => (vec![StackElement::Data(data.to_owned())], None), + ref instruction @ Instruction { + name: InstructionName::PUSHDEPLOYADDRESS, + .. + } => ( + vec![StackElement::value(instruction.name.to_string())], + None, + ), + + ref instruction @ Instruction { + name: InstructionName::ADD, + .. + } => { + let operands = &block_stack.elements[block_stack.elements.len() - 2..]; + + let result = match (&operands[1], &operands[0]) { + (Element::Tag(operand_1), Element::Constant(operand_2)) + | (Element::Constant(operand_1), Element::Tag(operand_2)) + | (Element::Tag(operand_1), Element::Tag(operand_2)) => { + match operand_1.checked_add(operand_2) { + Some(result) if Self::is_tag_value_valid(blocks, &result) => { + Element::Tag(result) + } + Some(_result) => Element::value(instruction.name.to_string()), + None => Element::value(instruction.name.to_string()), + } + } + (Element::Constant(operand_1), Element::Constant(operand_2)) => { + match operand_1.checked_add(operand_2) { + Some(result) => Element::Constant(result), + None => Element::value(instruction.name.to_string()), + } + } + _ => Element::value(instruction.name.to_string()), + }; + + (vec![result], None) + } + ref instruction @ Instruction { + name: InstructionName::SUB, + .. + } => { + let operands = &block_stack.elements[block_stack.elements.len() - 2..]; + + let result = match (&operands[1], &operands[0]) { + (Element::Tag(operand_1), Element::Constant(operand_2)) + | (Element::Constant(operand_1), Element::Tag(operand_2)) + | (Element::Tag(operand_1), Element::Tag(operand_2)) => { + match operand_1.checked_sub(operand_2) { + Some(result) if Self::is_tag_value_valid(blocks, &result) => { + Element::Tag(result) + } + Some(_result) => Element::value(instruction.name.to_string()), + None => Element::value(instruction.name.to_string()), + } + } + (Element::Constant(operand_1), Element::Constant(operand_2)) => { + match operand_1.checked_sub(operand_2) { + Some(result) => Element::Constant(result), + None => Element::value(instruction.name.to_string()), + } + } + _ => Element::value(instruction.name.to_string()), + }; + + (vec![result], None) + } + ref instruction @ Instruction { + name: InstructionName::MUL, + .. + } => { + let operands = &block_stack.elements[block_stack.elements.len() - 2..]; + + let result = match (&operands[1], &operands[0]) { + (Element::Tag(operand_1), Element::Constant(operand_2)) + | (Element::Constant(operand_1), Element::Tag(operand_2)) + | (Element::Tag(operand_1), Element::Tag(operand_2)) => { + match operand_1.checked_mul(operand_2) { + Some(result) if Self::is_tag_value_valid(blocks, &result) => { + Element::Tag(result) + } + Some(_result) => Element::value(instruction.name.to_string()), + None => Element::value(instruction.name.to_string()), + } + } + (Element::Constant(operand_1), Element::Constant(operand_2)) => { + match operand_1.checked_mul(operand_2) { + Some(result) => Element::Constant(result), + None => Element::value(instruction.name.to_string()), + } + } + _ => Element::value(instruction.name.to_string()), + }; + + (vec![result], None) + } + ref instruction @ Instruction { + name: InstructionName::DIV, + .. + } => { + let operands = &block_stack.elements[block_stack.elements.len() - 2..]; + + let result = match (&operands[1], &operands[0]) { + (Element::Tag(operand_1), Element::Constant(operand_2)) + | (Element::Constant(operand_1), Element::Tag(operand_2)) + | (Element::Tag(operand_1), Element::Tag(operand_2)) => { + if operand_2.is_zero() { + Element::Tag(num::BigUint::zero()) + } else { + match operand_1.checked_div(operand_2) { + Some(result) if Self::is_tag_value_valid(blocks, &result) => { + Element::Tag(result) + } + Some(_result) => Element::value(instruction.name.to_string()), + None => Element::value(instruction.name.to_string()), + } + } + } + (Element::Constant(operand_1), Element::Constant(operand_2)) => { + if operand_2.is_zero() { + Element::Constant(num::BigUint::zero()) + } else { + match operand_1.checked_div(operand_2) { + Some(result) => Element::Constant(result), + None => Element::value(instruction.name.to_string()), + } + } + } + _ => Element::value(instruction.name.to_string()), + }; + + (vec![result], None) + } + ref instruction @ Instruction { + name: InstructionName::MOD, + .. + } => { + let operands = &block_stack.elements[block_stack.elements.len() - 2..]; + + let result = match (&operands[1], &operands[0]) { + (Element::Tag(operand_1), Element::Constant(operand_2)) + | (Element::Constant(operand_1), Element::Tag(operand_2)) + | (Element::Tag(operand_1), Element::Tag(operand_2)) => { + if operand_2.is_zero() { + Element::Tag(num::BigUint::zero()) + } else { + let result = operand_1 % operand_2; + if Self::is_tag_value_valid(blocks, &result) { + Element::Tag(result) + } else { + Element::value(instruction.name.to_string()) + } + } + } + (Element::Constant(operand_1), Element::Constant(operand_2)) => { + if operand_2.is_zero() { + Element::Constant(num::BigUint::zero()) + } else { + Element::Constant(operand_1 % operand_2) + } + } + _ => Element::value(instruction.name.to_string()), + }; + + (vec![result], None) + } + ref instruction @ Instruction { + name: InstructionName::SHL, + .. + } => { + let operands = &block_stack.elements[block_stack.elements.len() - 2..]; + + let result = match (&operands[0], &operands[1]) { + (Element::Tag(tag), Element::Constant(offset)) => { + let offset = offset % era_compiler_common::BIT_LENGTH_FIELD; + let offset = offset.to_u64().expect("Always valid"); + let result = tag << offset; + if Self::is_tag_value_valid(blocks, &result) { + Element::Tag(result) + } else { + Element::value(instruction.name.to_string()) + } + } + (Element::Constant(constant), Element::Constant(offset)) => { + let offset = offset % era_compiler_common::BIT_LENGTH_FIELD; + let offset = offset.to_u64().expect("Always valid"); + Element::Constant(constant << offset) + } + _ => Element::value(instruction.name.to_string()), + }; + + (vec![result], None) + } + ref instruction @ Instruction { + name: InstructionName::SHR, + .. + } => { + let operands = &block_stack.elements[block_stack.elements.len() - 2..]; + + let result = match (&operands[0], &operands[1]) { + (Element::Tag(tag), Element::Constant(offset)) => { + let offset = offset % era_compiler_common::BIT_LENGTH_FIELD; + let offset = offset.to_u64().expect("Always valid"); + let result = tag >> offset; + if Self::is_tag_value_valid(blocks, &result) { + Element::Tag(result) + } else { + Element::value(instruction.name.to_string()) + } + } + (Element::Constant(constant), Element::Constant(offset)) => { + let offset = offset % era_compiler_common::BIT_LENGTH_FIELD; + let offset = offset.to_u64().expect("Always valid"); + Element::Constant(constant >> offset) + } + _ => Element::value(instruction.name.to_string()), + }; + + (vec![result], None) + } + ref instruction @ Instruction { + name: InstructionName::OR, + .. + } => { + let operands = &block_stack.elements[block_stack.elements.len() - 2..]; + + let result = match (&operands[1], &operands[0]) { + (Element::Tag(operand_1), Element::Tag(operand_2)) + | (Element::Tag(operand_1), Element::Constant(operand_2)) + | (Element::Constant(operand_1), Element::Tag(operand_2)) => { + let result = operand_1 | operand_2; + if Self::is_tag_value_valid(blocks, &result) { + Element::Tag(result) + } else { + Element::value(instruction.name.to_string()) + } + } + (Element::Constant(operand_1), Element::Constant(operand_2)) => { + Element::Constant(operand_1 | operand_2) + } + _ => Element::value(instruction.name.to_string()), + }; + + (vec![result], None) + } + ref instruction @ Instruction { + name: InstructionName::XOR, + .. + } => { + let operands = &block_stack.elements[block_stack.elements.len() - 2..]; + + let result = match (&operands[1], &operands[0]) { + (Element::Tag(operand_1), Element::Tag(operand_2)) + | (Element::Tag(operand_1), Element::Constant(operand_2)) + | (Element::Constant(operand_1), Element::Tag(operand_2)) => { + let result = operand_1 ^ operand_2; + if Self::is_tag_value_valid(blocks, &result) { + Element::Tag(result) + } else { + Element::value(instruction.name.to_string()) + } + } + (Element::Constant(operand_1), Element::Constant(operand_2)) => { + Element::Constant(operand_1 ^ operand_2) + } + _ => Element::value(instruction.name.to_string()), + }; + + (vec![result], None) + } + ref instruction @ Instruction { + name: InstructionName::AND, + .. + } => { + let operands = &block_stack.elements[block_stack.elements.len() - 2..]; + + let result = match (&operands[1], &operands[0]) { + (Element::Tag(operand_1), Element::Tag(operand_2)) + | (Element::Tag(operand_1), Element::Constant(operand_2)) + | (Element::Constant(operand_1), Element::Tag(operand_2)) => { + let result = operand_1 & operand_2; + if Self::is_tag_value_valid(blocks, &result) { + Element::Tag(result) + } else { + Element::value(instruction.name.to_string()) + } + } + (Element::Constant(operand_1), Element::Constant(operand_2)) => { + Element::Constant(operand_1 & operand_2) + } + _ => Element::value(instruction.name.to_string()), + }; + + (vec![result], None) + } + + ref instruction @ Instruction { + name: InstructionName::LT, + .. + } => { + let operands = &block_stack.elements[block_stack.elements.len() - 2..]; + + let result = match (&operands[1], &operands[0]) { + (Element::Tag(operand_1), Element::Tag(operand_2)) => { + Element::Constant(num::BigUint::from(u64::from(operand_1 < operand_2))) + } + _ => Element::value(instruction.name.to_string()), + }; + + (vec![result], None) + } + ref instruction @ Instruction { + name: InstructionName::GT, + .. + } => { + let operands = &block_stack.elements[block_stack.elements.len() - 2..]; + + let result = match (&operands[1], &operands[0]) { + (Element::Tag(operand_1), Element::Tag(operand_2)) => { + Element::Constant(num::BigUint::from(u64::from(operand_1 > operand_2))) + } + _ => Element::value(instruction.name.to_string()), + }; + + (vec![result], None) + } + ref instruction @ Instruction { + name: InstructionName::EQ, + .. + } => { + let operands = &block_stack.elements[block_stack.elements.len() - 2..]; + + let result = match (&operands[1], &operands[0]) { + (Element::Tag(operand_1), Element::Tag(operand_2)) => { + Element::Constant(num::BigUint::from(u64::from(operand_1 == operand_2))) + } + _ => Element::value(instruction.name.to_string()), + }; + + (vec![result], None) + } + ref instruction @ Instruction { + name: InstructionName::ISZERO, + .. + } => { + let operand = block_stack + .elements + .last() + .ok_or_else(|| anyhow::anyhow!("Operand is missing"))?; + + let result = match operand { + Element::Tag(operand) => Element::Constant(if operand.is_zero() { + num::BigUint::one() + } else { + num::BigUint::zero() + }), + _ => Element::value(instruction.name.to_string()), + }; + + (vec![result], None) + } + + ref instruction => ( + vec![Element::value(instruction.name.to_string()); instruction.output_size()], + None, + ), + }; + + Self::update_io_data( + block_stack, + block_element, + block_element.instruction.input_size(version), + stack_output, + )?; + + if let Some(mut queue_element) = queue_element { + queue_element.stack = block_element.stack.to_owned(); + queue.push(queue_element); + } + + Ok(()) + } + + /// + /// Updates the stack data with input and output data. + /// + fn update_io_data( + block_stack: &mut Stack, + block_element: &mut BlockElement, + input_size: usize, + output_data: Vec, + ) -> anyhow::Result<()> { + if block_stack.len() < input_size { + anyhow::bail!("Stack underflow"); + } + block_element.stack_input = Stack::new_with_elements( + block_stack + .elements + .drain(block_stack.len() - input_size..) + .collect(), + ); + block_element.stack_output = Stack::new_with_elements(output_data); + block_stack.append(&mut block_element.stack_output.clone()); + block_element.stack = block_stack.clone(); + Ok(()) + } + + /// + /// Handles the recursive function call. + /// + #[allow(clippy::too_many_arguments)] + fn handle_recursive_function_call( + recursive_function: &RecursiveFunction, + blocks: &HashMap, + functions: &mut BTreeMap, + extra_metadata: &ExtraMetadata, + visited_functions: &mut BTreeSet, + block_key: era_compiler_llvm_context::EraVMFunctionBlockKey, + block_stack: &mut Stack, + block_element: &mut BlockElement, + version: &semver::Version, + ) -> anyhow::Result<( + era_compiler_llvm_context::EraVMFunctionBlockKey, + Vec, + )> { + let return_address_offset = block_stack.elements.len() - 2 - recursive_function.input_size; + let input_arguments_offset = return_address_offset + 1; + let callee_tag_offset = input_arguments_offset + recursive_function.input_size; + + let return_address = match block_stack.elements[return_address_offset] { + Element::Tag(ref return_address) => { + era_compiler_llvm_context::EraVMFunctionBlockKey::new( + block_key.code_type, + return_address.to_owned(), + ) + } + ref element => anyhow::bail!("Expected the function return address, found {}", element), + }; + let mut stack = Stack::with_capacity(1 + recursive_function.input_size); + stack.push(StackElement::ReturnAddress( + 1 + recursive_function.output_size, + )); + stack.append(&mut Stack::new_with_elements( + block_stack.elements[input_arguments_offset..callee_tag_offset].to_owned(), + )); + let stack_hash = stack.hash(); + + let visited_element = VisitedElement::new(block_key.clone(), stack_hash); + if !visited_functions.contains(&visited_element) { + let mut function = Self::new( + version.to_owned(), + Type::new_recursive( + recursive_function.name.to_owned(), + block_key.clone(), + recursive_function.input_size, + recursive_function.output_size, + ), + ); + visited_functions.insert(visited_element); + function.traverse(blocks, functions, extra_metadata, visited_functions)?; + functions.insert(block_key.clone(), function); + } + + let stack_output = + vec![Element::value("RETURN_VALUE".to_owned()); recursive_function.output_size]; + let mut return_stack = Stack::new_with_elements( + block_stack.elements[..block_stack.len() - recursive_function.input_size - 2] + .to_owned(), + ); + return_stack.append(&mut Stack::new_with_elements(stack_output.clone())); + let return_stack_hash = return_stack.hash(); + + block_element.instruction = Instruction::recursive_call( + recursive_function.name.to_owned(), + block_key, + return_stack_hash, + recursive_function.input_size + 2, + recursive_function.output_size, + return_address.clone(), + &block_element.instruction, + ); + + Ok((return_address, stack_output)) + } + + /// + /// Pushes a block into the function. + /// + fn insert_block(&mut self, mut block: Block) -> &mut Block { + let key = block.key.clone(); + + if let Some(entry) = self.blocks.get_mut(&key) { + if entry.iter().all(|existing_block| { + existing_block.initial_stack.hash() != block.initial_stack.hash() + }) { + block.instance = Some(entry.len()); + entry.push(block); + } + } else { + block.instance = Some(0); + self.blocks.insert(block.key.clone(), vec![block]); + } + + self.blocks + .get_mut(&key) + .expect("Always exists") + .last_mut() + .expect("Always exists") + } + + /// + /// Checks whether the tag value actually references an existing block. + /// + /// Checks both deploy and runtime code. + /// + fn is_tag_value_valid( + blocks: &HashMap, + tag: &num::BigUint, + ) -> bool { + blocks.contains_key(&era_compiler_llvm_context::EraVMFunctionBlockKey::new( + era_compiler_llvm_context::EraVMCodeType::Deploy, + tag & num::BigUint::from(u32::MAX), + )) || blocks.contains_key(&era_compiler_llvm_context::EraVMFunctionBlockKey::new( + era_compiler_llvm_context::EraVMCodeType::Runtime, + tag & num::BigUint::from(u32::MAX), + )) + } + + /// + /// Finalizes the function data. + /// + fn finalize(&mut self) { + for (_tag, blocks) in self.blocks.iter() { + for block in blocks.iter() { + for block_element in block.elements.iter() { + let total_length = block_element.stack.elements.len() + + block_element.stack_input.len() + + block_element.stack_output.len(); + if total_length > self.stack_size { + self.stack_size = total_length; + } + } + } + } + } +} + +impl era_compiler_llvm_context::EraVMWriteLLVM for Function +where + D: era_compiler_llvm_context::EraVMDependency + Clone, +{ + fn declare( + &mut self, + context: &mut era_compiler_llvm_context::EraVMContext, + ) -> anyhow::Result<()> { + let (function_type, output_size) = match self.r#type { + Type::Initial => { + let output_size = 0; + let r#type = context.function_type( + vec![context + .integer_type(era_compiler_common::BIT_LENGTH_BOOLEAN) + .as_basic_type_enum()], + output_size, + false, + ); + (r#type, output_size) + } + Type::Recursive { + input_size, + output_size, + .. + } => { + let r#type = context.function_type( + vec![ + context + .integer_type(era_compiler_common::BIT_LENGTH_FIELD) + .as_basic_type_enum(); + input_size + ], + output_size, + false, + ); + (r#type, output_size) + } + }; + let function = context.add_function( + self.name.as_str(), + function_type, + output_size, + Some(inkwell::module::Linkage::Private), + )?; + function.borrow_mut().set_evmla_data( + era_compiler_llvm_context::EraVMFunctionEVMLAData::new(self.stack_size), + ); + + Ok(()) + } + + fn into_llvm( + self, + context: &mut era_compiler_llvm_context::EraVMContext, + ) -> anyhow::Result<()> { + context.set_current_function(self.name.as_str())?; + + for (key, blocks) in self.blocks.iter() { + for (index, block) in blocks.iter().enumerate() { + let inner = context.append_basic_block(format!("block_{key}/{index}").as_str()); + let mut stack_hashes = vec![block.initial_stack.hash()]; + stack_hashes.extend_from_slice(block.extra_hashes.as_slice()); + let evmla_data = + era_compiler_llvm_context::EraVMFunctionBlockEVMLAData::new(stack_hashes); + let mut block = era_compiler_llvm_context::EraVMFunctionBlock::new(inner); + block.set_evmla_data(evmla_data); + context + .current_function() + .borrow_mut() + .evmla_mut() + .insert_block(key.to_owned(), block); + } + } + + context.set_basic_block(context.current_function().borrow().entry_block()); + let mut stack_variables = Vec::with_capacity(self.stack_size); + for stack_index in 0..self.stack_size { + let pointer = context.build_alloca( + context.field_type(), + format!("stack_var_{stack_index:03}").as_str(), + ); + let value = match self.r#type { + Type::Recursive { input_size, .. } + if stack_index >= 1 && stack_index <= input_size => + { + context + .current_function() + .borrow() + .declaration() + .value + .get_nth_param((stack_index - 1) as u32) + .expect("Always valid") + } + _ => context.field_const(0).as_basic_value_enum(), + }; + context.build_store(pointer, value)?; + stack_variables.push(era_compiler_llvm_context::EraVMArgument::new( + pointer.value.as_basic_value_enum(), + )); + } + context.evmla_mut().stack = stack_variables; + + match self.r#type { + Type::Initial => { + let is_deploy_code_flag = context + .current_function() + .borrow() + .get_nth_param(0) + .into_int_value(); + let deploy_code_block = context.current_function().borrow().evmla().find_block( + &era_compiler_llvm_context::EraVMFunctionBlockKey::new( + era_compiler_llvm_context::EraVMCodeType::Deploy, + num::BigUint::zero(), + ), + &Stack::default().hash(), + )?; + let runtime_code_block = context.current_function().borrow().evmla().find_block( + &era_compiler_llvm_context::EraVMFunctionBlockKey::new( + era_compiler_llvm_context::EraVMCodeType::Runtime, + num::BigUint::zero(), + ), + &Stack::default().hash(), + )?; + context.build_conditional_branch( + is_deploy_code_flag, + deploy_code_block.inner(), + runtime_code_block.inner(), + )?; + } + Type::Recursive { ref block_key, .. } => { + let initial_block = context + .current_function() + .borrow() + .evmla() + .blocks + .get(block_key) + .expect("Always exists") + .first() + .expect("Always exists") + .inner(); + context.build_unconditional_branch(initial_block); + } + } + + for (key, blocks) in self.blocks.into_iter() { + for (llvm_block, ir_block) in context + .current_function() + .borrow() + .evmla() + .blocks + .get(&key) + .cloned() + .ok_or_else(|| anyhow::anyhow!("Undeclared function block {}", key))? + .into_iter() + .map(|block| block.inner()) + .zip(blocks) + { + context.set_basic_block(llvm_block); + ir_block.into_llvm(context)?; + } + } + + context.set_basic_block(context.current_function().borrow().return_block()); + match context.current_function().borrow().r#return() { + era_compiler_llvm_context::EraVMFunctionReturn::None => { + context.build_return(None); + } + era_compiler_llvm_context::EraVMFunctionReturn::Primitive { pointer } => { + let return_value = context.build_load(pointer, "return_value")?; + context.build_return(Some(&return_value)); + } + era_compiler_llvm_context::EraVMFunctionReturn::Compound { pointer, .. } => { + let return_value = context.build_load(pointer, "return_value")?; + context.build_return(Some(&return_value)); + } + } + + Ok(()) + } +} + +impl std::fmt::Display for Function { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self.r#type { + Type::Initial => writeln!(f, "function {} {{", self.name), + Type::Recursive { + input_size, + output_size, + .. + } => writeln!( + f, + "function {}({}) -> {} {{", + self.name, input_size, output_size + ), + }?; + writeln!(f, " stack_usage: {}", self.stack_size)?; + for (_key, blocks) in self.blocks.iter() { + for block in blocks.iter() { + write!(f, "{block}")?; + } + } + writeln!(f, "}}")?; + Ok(()) + } +} diff --git a/crates/solidity/src/evmla/ethereal_ir/function/queue_element.rs b/crates/solidity/src/evmla/ethereal_ir/function/queue_element.rs new file mode 100644 index 0000000..ccd370c --- /dev/null +++ b/crates/solidity/src/evmla/ethereal_ir/function/queue_element.rs @@ -0,0 +1,35 @@ +//! +//! The Ethereal IR block queue element. +//! + +use crate::evmla::ethereal_ir::function::block::element::stack::Stack; + +/// +/// The Ethereal IR block queue element. +/// +#[derive(Debug, Clone)] +pub struct QueueElement { + /// The block key. + pub block_key: era_compiler_llvm_context::EraVMFunctionBlockKey, + /// The block predecessor. + pub predecessor: Option<(era_compiler_llvm_context::EraVMFunctionBlockKey, usize)>, + /// The predecessor's last stack state. + pub stack: Stack, +} + +impl QueueElement { + /// + /// A shortcut constructor. + /// + pub fn new( + block_key: era_compiler_llvm_context::EraVMFunctionBlockKey, + predecessor: Option<(era_compiler_llvm_context::EraVMFunctionBlockKey, usize)>, + stack: Stack, + ) -> Self { + Self { + block_key, + predecessor, + stack, + } + } +} diff --git a/crates/solidity/src/evmla/ethereal_ir/function/type.rs b/crates/solidity/src/evmla/ethereal_ir/function/type.rs new file mode 100644 index 0000000..1cb374a --- /dev/null +++ b/crates/solidity/src/evmla/ethereal_ir/function/type.rs @@ -0,0 +1,49 @@ +//! +//! The Ethereal IR function type. +//! + +/// +/// The Ethereal IR function type. +/// +#[derive(Debug, Clone)] +pub enum Type { + /// The initial function, combining deploy and runtime code. + Initial, + /// The recursive function with a specific block starting its recursive context. + Recursive { + /// The function name. + name: String, + /// The function initial block key. + block_key: era_compiler_llvm_context::EraVMFunctionBlockKey, + /// The size of stack input (in cells or 256-bit words). + input_size: usize, + /// The size of stack output (in cells or 256-bit words). + output_size: usize, + }, +} + +impl Type { + /// + /// A shortcut constructor. + /// + pub fn new_initial() -> Self { + Self::Initial + } + + /// + /// A shortcut constructor. + /// + pub fn new_recursive( + name: String, + block_key: era_compiler_llvm_context::EraVMFunctionBlockKey, + input_size: usize, + output_size: usize, + ) -> Self { + Self::Recursive { + name, + block_key, + input_size, + output_size, + } + } +} diff --git a/crates/solidity/src/evmla/ethereal_ir/function/visited_element.rs b/crates/solidity/src/evmla/ethereal_ir/function/visited_element.rs new file mode 100644 index 0000000..fd7d4f3 --- /dev/null +++ b/crates/solidity/src/evmla/ethereal_ir/function/visited_element.rs @@ -0,0 +1,71 @@ +//! +//! The Ethereal IR block visited element. +//! + +use std::cmp::Ordering; + +/// +/// The Ethereal IR block visited element. +/// +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct VisitedElement { + /// The block key. + pub block_key: era_compiler_llvm_context::EraVMFunctionBlockKey, + /// The initial stack state hash. + pub stack_hash: md5::Digest, +} + +impl VisitedElement { + /// + /// A shortcut constructor. + /// + pub fn new( + block_key: era_compiler_llvm_context::EraVMFunctionBlockKey, + stack_hash: md5::Digest, + ) -> Self { + Self { + block_key, + stack_hash, + } + } +} + +impl PartialOrd for VisitedElement { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for VisitedElement { + fn cmp(&self, other: &Self) -> Ordering { + match (self.block_key.code_type, other.block_key.code_type) { + ( + era_compiler_llvm_context::EraVMCodeType::Deploy, + era_compiler_llvm_context::EraVMCodeType::Runtime, + ) => Ordering::Less, + ( + era_compiler_llvm_context::EraVMCodeType::Runtime, + era_compiler_llvm_context::EraVMCodeType::Deploy, + ) => Ordering::Greater, + ( + era_compiler_llvm_context::EraVMCodeType::Deploy, + era_compiler_llvm_context::EraVMCodeType::Deploy, + ) + | ( + era_compiler_llvm_context::EraVMCodeType::Runtime, + era_compiler_llvm_context::EraVMCodeType::Runtime, + ) => { + let tag_comparison = self.block_key.tag.cmp(&other.block_key.tag); + if tag_comparison == Ordering::Equal { + if self.stack_hash == other.stack_hash { + Ordering::Equal + } else { + Ordering::Less + } + } else { + tag_comparison + } + } + } + } +} diff --git a/crates/solidity/src/evmla/ethereal_ir/mod.rs b/crates/solidity/src/evmla/ethereal_ir/mod.rs new file mode 100644 index 0000000..5a9ddbf --- /dev/null +++ b/crates/solidity/src/evmla/ethereal_ir/mod.rs @@ -0,0 +1,150 @@ +//! +//! The Ethereal IR of the EVM bytecode. +//! + +pub mod entry_link; +pub mod function; + +use std::collections::BTreeMap; +use std::collections::BTreeSet; +use std::collections::HashMap; + +use crate::evmla::assembly::instruction::Instruction; +use crate::solc::standard_json::output::contract::evm::extra_metadata::ExtraMetadata; + +use self::function::block::Block; +use self::function::r#type::Type as FunctionType; +use self::function::Function; + +/// +/// The Ethereal IR of the EVM bytecode. +/// +/// The Ethereal IR (EthIR) is a special IR between the EVM legacy assembly and LLVM IR. It is +/// created to facilitate the translation and provide an additional environment for applying some +/// transformations, duplicating parts of the call and control flow graphs, tracking the +/// data flow, and a few more algorithms of static analysis. +/// +/// The most important feature of EthIR is flattening the block tags and duplicating blocks for +/// each of initial states of the stack. The LLVM IR supports only static control flow, so the +/// stack state must be known all the way throughout the program. +/// +#[derive(Debug)] +pub struct EtherealIR { + /// The Solidity compiler version. + pub solc_version: semver::Version, + /// The EVMLA extra metadata. + pub extra_metadata: ExtraMetadata, + /// The all-inlined function. + pub entry_function: Function, + /// The recursive functions. + pub recursive_functions: BTreeMap, +} + +impl EtherealIR { + /// The default entry function name. + pub const DEFAULT_ENTRY_FUNCTION_NAME: &'static str = "main"; + + /// The blocks hashmap initial capacity. + pub const BLOCKS_HASHMAP_DEFAULT_CAPACITY: usize = 64; + + /// + /// Assembles a sequence of functions from the sequence of instructions. + /// + pub fn new( + solc_version: semver::Version, + extra_metadata: ExtraMetadata, + blocks: HashMap, + ) -> anyhow::Result { + let mut entry_function = Function::new(solc_version.clone(), FunctionType::new_initial()); + let mut recursive_functions = BTreeMap::new(); + let mut visited_functions = BTreeSet::new(); + entry_function.traverse( + &blocks, + &mut recursive_functions, + &extra_metadata, + &mut visited_functions, + )?; + + Ok(Self { + solc_version, + extra_metadata, + entry_function, + recursive_functions, + }) + } + + /// + /// Gets blocks for the specified type of the contract code. + /// + pub fn get_blocks( + solc_version: semver::Version, + code_type: era_compiler_llvm_context::EraVMCodeType, + instructions: &[Instruction], + ) -> anyhow::Result> { + let mut blocks = HashMap::with_capacity(Self::BLOCKS_HASHMAP_DEFAULT_CAPACITY); + let mut offset = 0; + + while offset < instructions.len() { + let (block, size) = Block::try_from_instructions( + solc_version.clone(), + code_type, + &instructions[offset..], + )?; + blocks.insert( + era_compiler_llvm_context::EraVMFunctionBlockKey::new( + code_type, + block.key.tag.clone(), + ), + block, + ); + offset += size; + } + + Ok(blocks) + } +} + +impl era_compiler_llvm_context::EraVMWriteLLVM for EtherealIR +where + D: era_compiler_llvm_context::EraVMDependency + Clone, +{ + fn declare( + &mut self, + context: &mut era_compiler_llvm_context::EraVMContext, + ) -> anyhow::Result<()> { + self.entry_function.declare(context)?; + + for (_key, function) in self.recursive_functions.iter_mut() { + function.declare(context)?; + } + + Ok(()) + } + + fn into_llvm( + self, + context: &mut era_compiler_llvm_context::EraVMContext, + ) -> anyhow::Result<()> { + context.evmla_mut().stack = vec![]; + + self.entry_function.into_llvm(context)?; + + for (_key, function) in self.recursive_functions.into_iter() { + function.into_llvm(context)?; + } + + Ok(()) + } +} + +impl std::fmt::Display for EtherealIR { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + writeln!(f, "{}", self.entry_function)?; + + for (_key, function) in self.recursive_functions.iter() { + writeln!(f, "{}", function)?; + } + + Ok(()) + } +} diff --git a/crates/solidity/src/evmla/mod.rs b/crates/solidity/src/evmla/mod.rs new file mode 100644 index 0000000..68c8760 --- /dev/null +++ b/crates/solidity/src/evmla/mod.rs @@ -0,0 +1,6 @@ +//! +//! The EVM legacy assembly compiling tools. +//! + +pub mod assembly; +pub mod ethereal_ir; diff --git a/crates/solidity/src/lib.rs b/crates/solidity/src/lib.rs new file mode 100644 index 0000000..d670a1c --- /dev/null +++ b/crates/solidity/src/lib.rs @@ -0,0 +1,396 @@ +//! +//! Solidity to EraVM compiler library. +//! + +pub(crate) mod build; +pub(crate) mod r#const; +pub(crate) mod evmla; +pub(crate) mod missing_libraries; +pub(crate) mod process; +pub(crate) mod project; +pub(crate) mod solc; +pub(crate) mod warning; +pub(crate) mod yul; + +pub use self::build::contract::Contract as ContractBuild; +pub use self::build::Build; +pub use self::missing_libraries::MissingLibraries; +pub use self::process::input::Input as ProcessInput; +pub use self::process::output::Output as ProcessOutput; +pub use self::process::run as run_process; +pub use self::process::EXECUTABLE; +pub use self::project::contract::Contract as ProjectContract; +pub use self::project::Project; +pub use self::r#const::*; +pub use self::solc::combined_json::contract::Contract as SolcCombinedJsonContract; +pub use self::solc::combined_json::CombinedJson as SolcCombinedJson; +pub use self::solc::pipeline::Pipeline as SolcPipeline; +pub use self::solc::standard_json::input::language::Language as SolcStandardJsonInputLanguage; +pub use self::solc::standard_json::input::settings::metadata::Metadata as SolcStandardJsonInputSettingsMetadata; +pub use self::solc::standard_json::input::settings::optimizer::Optimizer as SolcStandardJsonInputSettingsOptimizer; +pub use self::solc::standard_json::input::settings::selection::file::flag::Flag as SolcStandardJsonInputSettingsSelectionFileFlag; +pub use self::solc::standard_json::input::settings::selection::file::File as SolcStandardJsonInputSettingsSelectionFile; +pub use self::solc::standard_json::input::settings::selection::Selection as SolcStandardJsonInputSettingsSelection; +pub use self::solc::standard_json::input::settings::Settings as SolcStandardJsonInputSettings; +pub use self::solc::standard_json::input::source::Source as SolcStandardJsonInputSource; +pub use self::solc::standard_json::input::Input as SolcStandardJsonInput; +pub use self::solc::standard_json::output::contract::evm::bytecode::Bytecode as SolcStandardJsonOutputContractEVMBytecode; +pub use self::solc::standard_json::output::contract::evm::EVM as SolcStandardJsonOutputContractEVM; +pub use self::solc::standard_json::output::contract::Contract as SolcStandardJsonOutputContract; +pub use self::solc::standard_json::output::Output as SolcStandardJsonOutput; +pub use self::solc::version::Version as SolcVersion; +pub use self::solc::Compiler as SolcCompiler; +pub use self::warning::Warning; + +pub mod test_utils; +pub mod tests; + +use std::collections::BTreeSet; +use std::path::PathBuf; + +/// +/// Runs the Yul mode. +/// +pub fn yul( + input_files: &[PathBuf], + solc: &mut SolcCompiler, + optimizer_settings: era_compiler_llvm_context::OptimizerSettings, + is_system_mode: bool, + include_metadata_hash: bool, + debug_config: Option, +) -> anyhow::Result { + let path = match input_files.len() { + 1 => input_files.first().expect("Always exists"), + 0 => anyhow::bail!("The input file is missing"), + length => anyhow::bail!( + "Only one input file is allowed in the Yul mode, but found {}", + length, + ), + }; + + let solc_validator = if is_system_mode { + None + } else { + if solc.version()?.default != SolcCompiler::LAST_SUPPORTED_VERSION { + anyhow::bail!( + "The Yul mode is only supported with the most recent version of the Solidity compiler: {}", + SolcCompiler::LAST_SUPPORTED_VERSION, + ); + } + + Some(&*solc) + }; + + let project = Project::try_from_yul_path(path, solc_validator)?; + + let build = project.compile( + optimizer_settings, + is_system_mode, + include_metadata_hash, + false, + debug_config, + )?; + + Ok(build) +} + +/// +/// Runs the LLVM IR mode. +/// +pub fn llvm_ir( + input_files: &[PathBuf], + optimizer_settings: era_compiler_llvm_context::OptimizerSettings, + is_system_mode: bool, + include_metadata_hash: bool, + debug_config: Option, +) -> anyhow::Result { + let path = match input_files.len() { + 1 => input_files.first().expect("Always exists"), + 0 => anyhow::bail!("The input file is missing"), + length => anyhow::bail!( + "Only one input file is allowed in the LLVM IR mode, but found {}", + length, + ), + }; + + let project = Project::try_from_llvm_ir_path(path)?; + + let build = project.compile( + optimizer_settings, + is_system_mode, + include_metadata_hash, + false, + debug_config, + )?; + + Ok(build) +} + +/// +/// Runs the EraVM assembly mode. +/// +pub fn zkasm( + input_files: &[PathBuf], + include_metadata_hash: bool, + debug_config: Option, +) -> anyhow::Result { + let path = match input_files.len() { + 1 => input_files.first().expect("Always exists"), + 0 => anyhow::bail!("The input file is missing"), + length => anyhow::bail!( + "Only one input file is allowed in the EraVM assembly mode, but found {}", + length, + ), + }; + + let project = Project::try_from_zkasm_path(path)?; + + let optimizer_settings = era_compiler_llvm_context::OptimizerSettings::none(); + let build = project.compile( + optimizer_settings, + false, + include_metadata_hash, + false, + debug_config, + )?; + + Ok(build) +} + +/// +/// Runs the standard output mode. +/// +#[allow(clippy::too_many_arguments)] +pub fn standard_output( + input_files: &[PathBuf], + libraries: Vec, + solc: &mut SolcCompiler, + evm_version: Option, + solc_optimizer_enabled: bool, + optimizer_settings: era_compiler_llvm_context::OptimizerSettings, + force_evmla: bool, + is_system_mode: bool, + include_metadata_hash: bool, + base_path: Option, + include_paths: Vec, + allow_paths: Option, + remappings: Option>, + suppressed_warnings: Option>, + debug_config: Option, +) -> anyhow::Result { + let solc_version = solc.version()?; + let solc_pipeline = SolcPipeline::new(&solc_version, force_evmla); + + let solc_input = SolcStandardJsonInput::try_from_paths( + SolcStandardJsonInputLanguage::Solidity, + evm_version, + input_files, + libraries, + remappings, + SolcStandardJsonInputSettingsSelection::new_required(solc_pipeline), + SolcStandardJsonInputSettingsOptimizer::new( + solc_optimizer_enabled, + None, + &solc_version.default, + optimizer_settings.is_fallback_to_size_enabled(), + optimizer_settings.is_system_request_memoization_disabled(), + ), + None, + solc_pipeline == SolcPipeline::Yul, + suppressed_warnings, + )?; + + let source_code_files = solc_input + .sources + .iter() + .map(|(path, source)| (path.to_owned(), source.content.to_owned())) + .collect(); + + let libraries = solc_input.settings.libraries.clone().unwrap_or_default(); + let mut solc_output = solc.standard_json( + solc_input, + solc_pipeline, + base_path, + include_paths, + allow_paths, + )?; + + if let Some(errors) = solc_output.errors.as_deref() { + let mut has_errors = false; + + for error in errors.iter() { + if error.severity.as_str() == "error" { + has_errors = true; + } + + eprintln!("{error}"); + } + + if has_errors { + anyhow::bail!("Error(s) found. Compilation aborted"); + } + } + + let project = solc_output.try_to_project( + source_code_files, + libraries, + solc_pipeline, + &solc_version, + debug_config.as_ref(), + )?; + + let build = project.compile( + optimizer_settings, + is_system_mode, + include_metadata_hash, + false, + debug_config, + )?; + + Ok(build) +} + +/// +/// Runs the standard JSON mode. +/// +#[allow(clippy::too_many_arguments)] +pub fn standard_json( + solc: &mut SolcCompiler, + detect_missing_libraries: bool, + force_evmla: bool, + is_system_mode: bool, + base_path: Option, + include_paths: Vec, + allow_paths: Option, + debug_config: Option, +) -> anyhow::Result<()> { + let solc_version = solc.version()?; + let solc_pipeline = SolcPipeline::new(&solc_version, force_evmla); + let zksolc_version = semver::Version::parse(env!("CARGO_PKG_VERSION")).expect("Always valid"); + + let solc_input = SolcStandardJsonInput::try_from_stdin(solc_pipeline)?; + let source_code_files = solc_input + .sources + .iter() + .map(|(path, source)| (path.to_owned(), source.content.to_owned())) + .collect(); + + let optimizer_settings = + era_compiler_llvm_context::OptimizerSettings::try_from(&solc_input.settings.optimizer)?; + + let include_metadata_hash = match solc_input.settings.metadata { + Some(ref metadata) => { + metadata.bytecode_hash != Some(era_compiler_llvm_context::EraVMMetadataHash::None) + } + None => true, + }; + + let libraries = solc_input.settings.libraries.clone().unwrap_or_default(); + let mut solc_output = solc.standard_json( + solc_input, + solc_pipeline, + base_path, + include_paths, + allow_paths, + )?; + + if let Some(errors) = solc_output.errors.as_deref() { + for error in errors.iter() { + if error.severity.as_str() == "error" { + serde_json::to_writer(std::io::stdout(), &solc_output)?; + std::process::exit(0); + } + } + } + + let project = solc_output.try_to_project( + source_code_files, + libraries, + solc_pipeline, + &solc_version, + debug_config.as_ref(), + )?; + + if detect_missing_libraries { + let missing_libraries = project.get_missing_libraries(); + missing_libraries.write_to_standard_json( + &mut solc_output, + &solc_version, + &zksolc_version, + )?; + } else { + let build = project.compile( + optimizer_settings, + is_system_mode, + include_metadata_hash, + false, + debug_config, + )?; + build.write_to_standard_json(&mut solc_output, &solc_version, &zksolc_version)?; + } + serde_json::to_writer(std::io::stdout(), &solc_output)?; + std::process::exit(0); +} + +/// +/// Runs the combined JSON mode. +/// +#[allow(clippy::too_many_arguments)] +pub fn combined_json( + format: String, + input_files: &[PathBuf], + libraries: Vec, + solc: &mut SolcCompiler, + evm_version: Option, + solc_optimizer_enabled: bool, + optimizer_settings: era_compiler_llvm_context::OptimizerSettings, + force_evmla: bool, + is_system_mode: bool, + include_metadata_hash: bool, + base_path: Option, + include_paths: Vec, + allow_paths: Option, + remappings: Option>, + suppressed_warnings: Option>, + debug_config: Option, + output_directory: Option, + overwrite: bool, +) -> anyhow::Result<()> { + let zksolc_version = semver::Version::parse(env!("CARGO_PKG_VERSION")).expect("Always valid"); + + let build = standard_output( + input_files, + libraries, + solc, + evm_version, + solc_optimizer_enabled, + optimizer_settings, + force_evmla, + is_system_mode, + include_metadata_hash, + base_path, + include_paths, + allow_paths, + remappings, + suppressed_warnings, + debug_config, + )?; + + let mut combined_json = solc.combined_json(input_files, format.as_str())?; + build.write_to_combined_json(&mut combined_json, &zksolc_version)?; + + match output_directory { + Some(output_directory) => { + std::fs::create_dir_all(output_directory.as_path())?; + + combined_json.write_to_directory(output_directory.as_path(), overwrite)?; + } + None => { + println!( + "{}", + serde_json::to_string(&combined_json).expect("Always valid") + ); + } + } + std::process::exit(0); +} diff --git a/crates/solidity/src/missing_libraries.rs b/crates/solidity/src/missing_libraries.rs new file mode 100644 index 0000000..9f2ffeb --- /dev/null +++ b/crates/solidity/src/missing_libraries.rs @@ -0,0 +1,58 @@ +//! +//! The missing Solidity libraries. +//! + +use std::collections::BTreeMap; +use std::collections::HashSet; + +use crate::solc::standard_json::output::Output as StandardJsonOutput; +use crate::solc::version::Version as SolcVersion; + +/// +/// The missing Solidity libraries. +/// +pub struct MissingLibraries { + /// The missing libraries. + pub contract_libraries: BTreeMap>, +} + +impl MissingLibraries { + /// + /// A shortcut constructor. + /// + pub fn new(contract_libraries: BTreeMap>) -> Self { + Self { contract_libraries } + } + + /// + /// Writes the missing libraries to the standard JSON. + /// + pub fn write_to_standard_json( + mut self, + standard_json: &mut StandardJsonOutput, + solc_version: &SolcVersion, + zksolc_version: &semver::Version, + ) -> anyhow::Result<()> { + let contracts = match standard_json.contracts.as_mut() { + Some(contracts) => contracts, + None => return Ok(()), + }; + + for (path, contracts) in contracts.iter_mut() { + for (name, contract) in contracts.iter_mut() { + let full_name = format!("{path}:{name}"); + let missing_libraries = self.contract_libraries.remove(full_name.as_str()); + + if let Some(missing_libraries) = missing_libraries { + contract.missing_libraries = Some(missing_libraries); + } + } + } + + standard_json.version = Some(solc_version.default.to_string()); + standard_json.long_version = Some(solc_version.long.to_owned()); + standard_json.zk_version = Some(zksolc_version.to_string()); + + Ok(()) + } +} diff --git a/crates/solidity/src/process/input.rs b/crates/solidity/src/process/input.rs new file mode 100644 index 0000000..74f9b0f --- /dev/null +++ b/crates/solidity/src/process/input.rs @@ -0,0 +1,57 @@ +//! +//! Process for compiling a single compilation unit. +//! +//! The input data. +//! + +use serde::Deserialize; +use serde::Serialize; + +use crate::project::contract::Contract; +use crate::project::Project; + +/// +/// The input data. +/// +#[derive(Debug, Serialize, Deserialize)] +pub struct Input { + /// The contract representation. + pub contract: Contract, + /// The project representation. + pub project: Project, + /// The system mode flag. + pub is_system_mode: bool, + /// Whether to append the metadata hash. + pub include_metadata_hash: bool, + /// Enables the test bytecode encoding. + pub enable_test_encoding: bool, + /// The optimizer settings. + pub optimizer_settings: era_compiler_llvm_context::OptimizerSettings, + /// The debug output config. + pub debug_config: Option, +} + +impl Input { + /// + /// A shortcut constructor. + /// + pub fn new( + contract: Contract, + project: Project, + is_system_mode: bool, + include_metadata_hash: bool, + enable_test_encoding: bool, + optimizer_settings: era_compiler_llvm_context::OptimizerSettings, + debug_config: Option, + ) -> Self { + Self { + contract, + project, + is_system_mode, + include_metadata_hash, + enable_test_encoding, + optimizer_settings, + debug_config, + } + } +} diff --git a/crates/solidity/src/process/mod.rs b/crates/solidity/src/process/mod.rs new file mode 100644 index 0000000..cac721d --- /dev/null +++ b/crates/solidity/src/process/mod.rs @@ -0,0 +1,108 @@ +//! +//! Process for compiling a single compilation unit. +//! + +pub mod input; +pub mod output; + +use std::io::Read; +use std::io::Write; +use std::path::PathBuf; +use std::process::Command; + +use once_cell::sync::OnceCell; + +use self::input::Input; +use self::output::Output; + +/// The overriden executable name used when the compiler is run as a library. +pub static EXECUTABLE: OnceCell = OnceCell::new(); + +/// +/// Read input from `stdin`, compile a contract, and write the output to `stdout`. +/// +pub fn run() -> anyhow::Result<()> { + let mut stdin = std::io::stdin(); + let mut stdout = std::io::stdout(); + let mut stderr = std::io::stderr(); + + let mut buffer = Vec::with_capacity(16384); + stdin.read_to_end(&mut buffer).expect("Stdin reading error"); + + let input: Input = era_compiler_common::deserialize_from_slice(buffer.as_slice())?; + if input.enable_test_encoding { + todo!() + } + let result = input.contract.compile( + input.project, + input.optimizer_settings, + input.is_system_mode, + input.include_metadata_hash, + input.debug_config, + ); + + match result { + Ok(build) => { + let output = Output::new(build); + let json = serde_json::to_vec(&output).expect("Always valid"); + stdout + .write_all(json.as_slice()) + .expect("Stdout writing error"); + Ok(()) + } + Err(error) => { + let message = error.to_string(); + stderr + .write_all(message.as_bytes()) + .expect("Stderr writing error"); + Err(error) + } + } +} + +/// +/// Runs this process recursively to compile a single contract. +/// +pub fn call(input: Input) -> anyhow::Result { + let input_json = serde_json::to_vec(&input).expect("Always valid"); + + let executable = match EXECUTABLE.get() { + Some(executable) => executable.to_owned(), + None => std::env::current_exe()?, + }; + + let mut command = Command::new(executable.as_path()); + command.stdin(std::process::Stdio::piped()); + command.stdout(std::process::Stdio::piped()); + command.stderr(std::process::Stdio::piped()); + command.arg("--recursive-process"); + let process = command.spawn().map_err(|error| { + anyhow::anyhow!("{:?} subprocess spawning error: {:?}", executable, error) + })?; + + process + .stdin + .as_ref() + .ok_or_else(|| anyhow::anyhow!("{:?} stdin getting error", executable))? + .write_all(input_json.as_slice()) + .map_err(|error| anyhow::anyhow!("{:?} stdin writing error: {:?}", executable, error))?; + let output = process.wait_with_output().map_err(|error| { + anyhow::anyhow!("{:?} subprocess output error: {:?}", executable, error) + })?; + if !output.status.success() { + anyhow::bail!( + "{}", + String::from_utf8_lossy(output.stderr.as_slice()).to_string(), + ); + } + + let output: Output = era_compiler_common::deserialize_from_slice(output.stdout.as_slice()) + .map_err(|error| { + anyhow::anyhow!( + "{:?} subprocess output parsing error: {}", + executable, + error, + ) + })?; + Ok(output) +} diff --git a/crates/solidity/src/process/output.rs b/crates/solidity/src/process/output.rs new file mode 100644 index 0000000..f439489 --- /dev/null +++ b/crates/solidity/src/process/output.rs @@ -0,0 +1,28 @@ +//! +//! Process for compiling a single compilation unit. +//! +//! The output data. +//! + +use serde::Deserialize; +use serde::Serialize; + +use crate::build::contract::Contract as ContractBuild; + +/// +/// The output data. +/// +#[derive(Debug, Serialize, Deserialize)] +pub struct Output { + /// The contract build. + pub build: ContractBuild, +} + +impl Output { + /// + /// A shortcut constructor. + /// + pub fn new(build: ContractBuild) -> Self { + Self { build } + } +} diff --git a/crates/solidity/src/project/contract/ir/evmla.rs b/crates/solidity/src/project/contract/ir/evmla.rs new file mode 100644 index 0000000..3131fe3 --- /dev/null +++ b/crates/solidity/src/project/contract/ir/evmla.rs @@ -0,0 +1,58 @@ +//! +//! The contract EVM legacy assembly source code. +//! + +use std::collections::HashSet; + +use serde::Deserialize; +use serde::Serialize; + +use crate::evmla::assembly::Assembly; +use crate::solc::standard_json::output::contract::evm::extra_metadata::ExtraMetadata; + +/// +/// The contract EVM legacy assembly source code. +/// +#[derive(Debug, Serialize, Deserialize, Clone)] +#[allow(non_camel_case_types)] +#[allow(clippy::upper_case_acronyms)] +pub struct EVMLA { + /// The EVM legacy assembly source code. + pub assembly: Assembly, +} + +impl EVMLA { + /// + /// A shortcut constructor. + /// + pub fn new(mut assembly: Assembly, extra_metadata: ExtraMetadata) -> Self { + assembly.extra_metadata = Some(extra_metadata); + Self { assembly } + } + + /// + /// Get the list of missing deployable libraries. + /// + pub fn get_missing_libraries(&self) -> HashSet { + self.assembly.get_missing_libraries() + } +} + +impl era_compiler_llvm_context::EraVMWriteLLVM for EVMLA +where + D: era_compiler_llvm_context::EraVMDependency + Clone, +{ + fn declare( + &mut self, + context: &mut era_compiler_llvm_context::EraVMContext, + ) -> anyhow::Result<()> { + self.assembly.declare(context) + } + + fn into_llvm( + self, + context: &mut era_compiler_llvm_context::EraVMContext, + ) -> anyhow::Result<()> { + self.assembly.into_llvm(context) + } +} diff --git a/crates/solidity/src/project/contract/ir/llvm_ir.rs b/crates/solidity/src/project/contract/ir/llvm_ir.rs new file mode 100644 index 0000000..4db7ee5 --- /dev/null +++ b/crates/solidity/src/project/contract/ir/llvm_ir.rs @@ -0,0 +1,27 @@ +//! +//! The contract LLVM IR source code. +//! + +use serde::Deserialize; +use serde::Serialize; + +/// +/// The contract LLVM IR source code. +/// +#[derive(Debug, Serialize, Deserialize, Clone)] +#[allow(clippy::upper_case_acronyms)] +pub struct LLVMIR { + /// The LLVM IR file path. + pub path: String, + /// The LLVM IR source code. + pub source: String, +} + +impl LLVMIR { + /// + /// A shortcut constructor. + /// + pub fn new(path: String, source: String) -> Self { + Self { path, source } + } +} diff --git a/crates/solidity/src/project/contract/ir/mod.rs b/crates/solidity/src/project/contract/ir/mod.rs new file mode 100644 index 0000000..1a6c279 --- /dev/null +++ b/crates/solidity/src/project/contract/ir/mod.rs @@ -0,0 +1,111 @@ +//! +//! The contract source code. +//! + +pub mod evmla; +pub mod llvm_ir; +pub mod yul; +pub mod zkasm; + +use std::collections::HashSet; + +use serde::Deserialize; +use serde::Serialize; + +use crate::evmla::assembly::Assembly; +use crate::solc::standard_json::output::contract::evm::extra_metadata::ExtraMetadata; +use crate::yul::parser::statement::object::Object; + +use self::evmla::EVMLA; +use self::llvm_ir::LLVMIR; +use self::yul::Yul; +use self::zkasm::ZKASM; + +/// +/// The contract source code. +/// +#[derive(Debug, Serialize, Deserialize, Clone)] +#[allow(non_camel_case_types)] +#[allow(clippy::upper_case_acronyms)] +#[allow(clippy::enum_variant_names)] +pub enum IR { + /// The Yul source code. + Yul(Yul), + /// The EVM legacy assembly source code. + EVMLA(EVMLA), + /// The LLVM IR source code. + LLVMIR(LLVMIR), + /// The EraVM assembly source code. + ZKASM(ZKASM), +} + +impl IR { + /// + /// A shortcut constructor. + /// + pub fn new_yul(source_code: String, object: Object) -> Self { + Self::Yul(Yul::new(source_code, object)) + } + + /// + /// A shortcut constructor. + /// + pub fn new_evmla(assembly: Assembly, extra_metadata: ExtraMetadata) -> Self { + Self::EVMLA(EVMLA::new(assembly, extra_metadata)) + } + + /// + /// A shortcut constructor. + /// + pub fn new_llvm_ir(path: String, source: String) -> Self { + Self::LLVMIR(LLVMIR::new(path, source)) + } + + /// + /// A shortcut constructor. + /// + pub fn new_zkasm(path: String, source: String) -> Self { + Self::ZKASM(ZKASM::new(path, source)) + } + + /// + /// Get the list of missing deployable libraries. + /// + pub fn get_missing_libraries(&self) -> HashSet { + match self { + Self::Yul(inner) => inner.get_missing_libraries(), + Self::EVMLA(inner) => inner.get_missing_libraries(), + Self::LLVMIR(_inner) => HashSet::new(), + Self::ZKASM(_inner) => HashSet::new(), + } + } +} + +impl era_compiler_llvm_context::EraVMWriteLLVM for IR +where + D: era_compiler_llvm_context::EraVMDependency + Clone, +{ + fn declare( + &mut self, + context: &mut era_compiler_llvm_context::EraVMContext, + ) -> anyhow::Result<()> { + match self { + Self::Yul(inner) => inner.declare(context), + Self::EVMLA(inner) => inner.declare(context), + Self::LLVMIR(_inner) => Ok(()), + Self::ZKASM(_inner) => Ok(()), + } + } + + fn into_llvm( + self, + context: &mut era_compiler_llvm_context::EraVMContext, + ) -> anyhow::Result<()> { + match self { + Self::Yul(inner) => inner.into_llvm(context), + Self::EVMLA(inner) => inner.into_llvm(context), + Self::LLVMIR(_inner) => Ok(()), + Self::ZKASM(_inner) => Ok(()), + } + } +} diff --git a/crates/solidity/src/project/contract/ir/yul.rs b/crates/solidity/src/project/contract/ir/yul.rs new file mode 100644 index 0000000..f22886b --- /dev/null +++ b/crates/solidity/src/project/contract/ir/yul.rs @@ -0,0 +1,59 @@ +//! +//! The contract Yul source code. +//! + +use std::collections::HashSet; + +use serde::Deserialize; +use serde::Serialize; + +use crate::yul::parser::statement::object::Object; + +/// +/// The contract Yul source code. +/// +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct Yul { + /// The Yul source code. + pub source_code: String, + /// The Yul AST object. + pub object: Object, +} + +impl Yul { + /// + /// A shortcut constructor. + /// + pub fn new(source_code: String, object: Object) -> Self { + Self { + source_code, + object, + } + } + + /// + /// Get the list of missing deployable libraries. + /// + pub fn get_missing_libraries(&self) -> HashSet { + self.object.get_missing_libraries() + } +} + +impl era_compiler_llvm_context::EraVMWriteLLVM for Yul +where + D: era_compiler_llvm_context::EraVMDependency + Clone, +{ + fn declare( + &mut self, + context: &mut era_compiler_llvm_context::EraVMContext, + ) -> anyhow::Result<()> { + self.object.declare(context) + } + + fn into_llvm( + self, + context: &mut era_compiler_llvm_context::EraVMContext, + ) -> anyhow::Result<()> { + self.object.into_llvm(context) + } +} diff --git a/crates/solidity/src/project/contract/ir/zkasm.rs b/crates/solidity/src/project/contract/ir/zkasm.rs new file mode 100644 index 0000000..94360e2 --- /dev/null +++ b/crates/solidity/src/project/contract/ir/zkasm.rs @@ -0,0 +1,27 @@ +//! +//! The contract EraVM assembly source code. +//! + +use serde::Deserialize; +use serde::Serialize; + +/// +/// The contract EraVM assembly source code. +/// +#[derive(Debug, Serialize, Deserialize, Clone)] +#[allow(clippy::upper_case_acronyms)] +pub struct ZKASM { + /// The EraVM assembly file path. + pub path: String, + /// The EraVM assembly source code. + pub source: String, +} + +impl ZKASM { + /// + /// A shortcut constructor. + /// + pub fn new(path: String, source: String) -> Self { + Self { path, source } + } +} diff --git a/crates/solidity/src/project/contract/metadata.rs b/crates/solidity/src/project/contract/metadata.rs new file mode 100644 index 0000000..0c68175 --- /dev/null +++ b/crates/solidity/src/project/contract/metadata.rs @@ -0,0 +1,45 @@ +//! +//! The Solidity contract metadata. +//! + +use serde::Serialize; + +/// +/// The Solidity contract metadata. +/// +/// Is used to append the metadata hash to the contract bytecode. +/// +#[derive(Debug, Serialize)] +pub struct Metadata { + /// The `solc` metadata. + pub solc_metadata: serde_json::Value, + /// The `solc` version. + pub solc_version: semver::Version, + /// The zkVM `solc` edition. + pub solc_zkvm_edition: Option, + /// The EraVM compiler version. + pub zk_version: semver::Version, + /// The EraVM compiler optimizer settings. + pub optimizer_settings: era_compiler_llvm_context::OptimizerSettings, +} + +impl Metadata { + /// + /// A shortcut constructor. + /// + pub fn new( + solc_metadata: serde_json::Value, + solc_version: semver::Version, + solc_zkvm_edition: Option, + zk_version: semver::Version, + optimizer_settings: era_compiler_llvm_context::OptimizerSettings, + ) -> Self { + Self { + solc_metadata, + solc_version, + solc_zkvm_edition, + zk_version, + optimizer_settings, + } + } +} diff --git a/crates/solidity/src/project/contract/mod.rs b/crates/solidity/src/project/contract/mod.rs new file mode 100644 index 0000000..8206d37 --- /dev/null +++ b/crates/solidity/src/project/contract/mod.rs @@ -0,0 +1,224 @@ +//! +//! The contract data. +//! + +pub mod ir; +pub mod metadata; + +use std::collections::HashSet; + +use serde::Deserialize; +use serde::Serialize; +use sha3::Digest; + +use era_compiler_llvm_context::EraVMWriteLLVM; + +use crate::build::contract::Contract as ContractBuild; +use crate::project::Project; +use crate::solc::version::Version as SolcVersion; + +use self::ir::IR; +use self::metadata::Metadata; + +/// +/// The contract data. +/// +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct Contract { + /// The absolute file path. + pub path: String, + /// The IR source code data. + pub ir: IR, + /// The metadata JSON. + pub metadata_json: serde_json::Value, +} + +impl Contract { + /// + /// A shortcut constructor. + /// + pub fn new( + path: String, + source_hash: [u8; era_compiler_common::BYTE_LENGTH_FIELD], + source_version: SolcVersion, + ir: IR, + metadata_json: Option, + ) -> Self { + let metadata_json = metadata_json.unwrap_or_else(|| { + serde_json::json!({ + "source_hash": hex::encode(source_hash.as_slice()), + "source_version": serde_json::to_value(&source_version).expect("Always valid"), + }) + }); + + Self { + path, + ir, + metadata_json, + } + } + + /// + /// Returns the contract identifier, which is: + /// - the Yul object identifier for Yul + /// - the full contract path for EVM legacy assembly + /// - the module name for LLVM IR + /// + pub fn identifier(&self) -> &str { + match self.ir { + IR::Yul(ref yul) => yul.object.identifier.as_str(), + IR::EVMLA(ref evm) => evm.assembly.full_path(), + IR::LLVMIR(ref llvm_ir) => llvm_ir.path.as_str(), + IR::ZKASM(ref zkasm) => zkasm.path.as_str(), + } + } + + /// + /// Extract factory dependencies. + /// + pub fn drain_factory_dependencies(&mut self) -> HashSet { + match self.ir { + IR::Yul(ref mut yul) => yul.object.factory_dependencies.drain().collect(), + IR::EVMLA(ref mut evm) => evm.assembly.factory_dependencies.drain().collect(), + IR::LLVMIR(_) => HashSet::new(), + IR::ZKASM(_) => HashSet::new(), + } + } + + /// + /// Compiles the specified contract, setting its build artifacts. + /// + pub fn compile( + mut self, + project: Project, + optimizer_settings: era_compiler_llvm_context::OptimizerSettings, + is_system_mode: bool, + include_metadata_hash: bool, + debug_config: Option, + ) -> anyhow::Result { + let llvm = inkwell::context::Context::create(); + let optimizer = era_compiler_llvm_context::Optimizer::new(optimizer_settings); + + let version = project.version.clone(); + let identifier = self.identifier().to_owned(); + + let metadata = Metadata::new( + self.metadata_json.take(), + version.default.clone(), + version.l2_revision.clone(), + semver::Version::parse(env!("CARGO_PKG_VERSION")).expect("Always valid"), + optimizer.settings().to_owned(), + ); + let metadata_json = serde_json::to_value(&metadata).expect("Always valid"); + let metadata_hash: Option<[u8; era_compiler_common::BYTE_LENGTH_FIELD]> = + if include_metadata_hash { + let metadata_string = serde_json::to_string(&metadata).expect("Always valid"); + Some(sha3::Keccak256::digest(metadata_string.as_bytes()).into()) + } else { + None + }; + + let module = match self.ir { + IR::LLVMIR(ref llvm_ir) => { + let memory_buffer = + inkwell::memory_buffer::MemoryBuffer::create_from_memory_range_copy( + llvm_ir.source.as_bytes(), + self.path.as_str(), + ); + llvm.create_module_from_ir(memory_buffer) + .map_err(|error| anyhow::anyhow!(error.to_string()))? + } + IR::ZKASM(ref zkasm) => { + let build = era_compiler_llvm_context::eravm_build_assembly_text( + self.path.as_str(), + zkasm.source.as_str(), + metadata_hash, + debug_config.as_ref(), + )?; + return Ok(ContractBuild::new( + self.path, + identifier, + build, + metadata_json, + HashSet::new(), + )); + } + _ => llvm.create_module(self.path.as_str()), + }; + let mut context = era_compiler_llvm_context::EraVMContext::new( + &llvm, + module, + optimizer, + Some(project), + include_metadata_hash, + debug_config, + ); + context.set_solidity_data(era_compiler_llvm_context::EraVMContextSolidityData::default()); + match self.ir { + IR::Yul(_) => { + let yul_data = era_compiler_llvm_context::EraVMContextYulData::new(is_system_mode); + context.set_yul_data(yul_data); + } + IR::EVMLA(_) => { + let evmla_data = + era_compiler_llvm_context::EraVMContextEVMLAData::new(version.default); + context.set_evmla_data(evmla_data); + } + IR::LLVMIR(_) => {} + IR::ZKASM(_) => {} + } + + let factory_dependencies = self.drain_factory_dependencies(); + + self.ir.declare(&mut context).map_err(|error| { + anyhow::anyhow!( + "The contract `{}` LLVM IR generator declaration pass error: {}", + self.path, + error + ) + })?; + self.ir.into_llvm(&mut context).map_err(|error| { + anyhow::anyhow!( + "The contract `{}` LLVM IR generator definition pass error: {}", + self.path, + error + ) + })?; + + let build = context.build(self.path.as_str(), metadata_hash)?; + + Ok(ContractBuild::new( + self.path, + identifier, + build, + metadata_json, + factory_dependencies, + )) + } + + /// + /// Get the list of missing deployable libraries. + /// + pub fn get_missing_libraries(&self) -> HashSet { + self.ir.get_missing_libraries() + } +} + +impl EraVMWriteLLVM for Contract +where + D: era_compiler_llvm_context::EraVMDependency + Clone, +{ + fn declare( + &mut self, + context: &mut era_compiler_llvm_context::EraVMContext, + ) -> anyhow::Result<()> { + self.ir.declare(context) + } + + fn into_llvm( + self, + context: &mut era_compiler_llvm_context::EraVMContext, + ) -> anyhow::Result<()> { + self.ir.into_llvm(context) + } +} diff --git a/crates/solidity/src/project/mod.rs b/crates/solidity/src/project/mod.rs new file mode 100644 index 0000000..851e0db --- /dev/null +++ b/crates/solidity/src/project/mod.rs @@ -0,0 +1,351 @@ +//! +//! The processed input data. +//! + +pub mod contract; + +use std::collections::BTreeMap; +use std::collections::HashMap; +use std::collections::HashSet; +use std::path::Path; + +use rayon::iter::IntoParallelIterator; +use rayon::iter::ParallelIterator; +use serde::Deserialize; +use serde::Serialize; +use sha3::Digest; + +use crate::build::contract::Contract as ContractBuild; +use crate::build::Build; +use crate::missing_libraries::MissingLibraries; +use crate::process::input::Input as ProcessInput; +use crate::project::contract::ir::IR; +use crate::solc::version::Version as SolcVersion; +use crate::solc::Compiler as SolcCompiler; +use crate::yul::lexer::Lexer; +use crate::yul::parser::statement::object::Object; + +use self::contract::Contract; + +/// +/// The processes input data. +/// +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct Project { + /// The source code version. + pub version: SolcVersion, + /// The project contracts, + pub contracts: BTreeMap, + /// The mapping of auxiliary identifiers, e.g. Yul object names, to full contract paths. + pub identifier_paths: BTreeMap, + /// The library addresses. + pub libraries: BTreeMap>, +} + +impl Project { + /// + /// A shortcut constructor. + /// + pub fn new( + version: SolcVersion, + contracts: BTreeMap, + libraries: BTreeMap>, + ) -> Self { + let mut identifier_paths = BTreeMap::new(); + for (path, contract) in contracts.iter() { + identifier_paths.insert(contract.identifier().to_owned(), path.to_owned()); + } + + Self { + version, + contracts, + identifier_paths, + libraries, + } + } + + /// + /// Compiles all contracts, returning their build artifacts. + /// + pub fn compile( + self, + optimizer_settings: era_compiler_llvm_context::OptimizerSettings, + is_system_mode: bool, + include_metadata_hash: bool, + bytecode_encoding_testing: bool, + debug_config: Option, + ) -> anyhow::Result { + let project = self.clone(); + let results: BTreeMap> = self + .contracts + .into_par_iter() + .map(|(full_path, contract)| { + let process_output = crate::process::call(ProcessInput::new( + contract, + project.clone(), + is_system_mode, + include_metadata_hash, + bytecode_encoding_testing, + optimizer_settings.clone(), + debug_config.clone(), + )); + + (full_path, process_output.map(|output| output.build)) + }) + .collect(); + + let mut build = Build::default(); + let mut hashes = HashMap::with_capacity(results.len()); + for (path, result) in results.iter() { + match result { + Ok(contract) => { + hashes.insert(path.to_owned(), contract.build.bytecode_hash.to_owned()); + } + Err(error) => { + anyhow::bail!("Contract `{}` compiling error: {:?}", path, error); + } + } + } + for (path, result) in results.into_iter() { + match result { + Ok(mut contract) => { + for dependency in contract.factory_dependencies.drain() { + let dependency_path = project + .identifier_paths + .get(dependency.as_str()) + .cloned() + .unwrap_or_else(|| { + panic!("Dependency `{dependency}` full path not found") + }); + let hash = match hashes.get(dependency_path.as_str()) { + Some(hash) => hash.to_owned(), + None => anyhow::bail!( + "Dependency contract `{}` not found in the project", + dependency_path + ), + }; + contract + .build + .factory_dependencies + .insert(hash, dependency_path); + } + + build.contracts.insert(path, contract); + } + Err(error) => { + anyhow::bail!("Contract `{}` compiling error: {:?}", path, error); + } + } + } + + Ok(build) + } + + /// + /// Get the list of missing deployable libraries. + /// + pub fn get_missing_libraries(&self) -> MissingLibraries { + let deployed_libraries = self + .libraries + .iter() + .flat_map(|(file, names)| { + names + .iter() + .map(|(name, _address)| format!("{file}:{name}")) + .collect::>() + }) + .collect::>(); + + let mut missing_deployable_libraries = BTreeMap::new(); + for (contract_path, contract) in self.contracts.iter() { + let missing_libraries = contract + .get_missing_libraries() + .into_iter() + .filter(|library| !deployed_libraries.contains(library)) + .collect::>(); + missing_deployable_libraries.insert(contract_path.to_owned(), missing_libraries); + } + MissingLibraries::new(missing_deployable_libraries) + } + + /// + /// Parses the Yul source code file and returns the source data. + /// + pub fn try_from_yul_path( + path: &Path, + solc_validator: Option<&SolcCompiler>, + ) -> anyhow::Result { + let source_code = std::fs::read_to_string(path) + .map_err(|error| anyhow::anyhow!("Yul file {:?} reading error: {}", path, error))?; + Self::try_from_yul_string(path, source_code.as_str(), solc_validator) + } + + /// + /// Parses the test Yul source code string and returns the source data. + /// + /// Only for integration testing purposes. + /// + pub fn try_from_yul_string( + path: &Path, + source_code: &str, + solc_validator: Option<&SolcCompiler>, + ) -> anyhow::Result { + if let Some(solc) = solc_validator { + solc.validate_yul(path)?; + } + + let source_version = SolcVersion::new_simple(SolcCompiler::LAST_SUPPORTED_VERSION); + let path = path.to_string_lossy().to_string(); + let source_hash = sha3::Keccak256::digest(source_code.as_bytes()).into(); + + let mut lexer = Lexer::new(source_code.to_owned()); + let object = Object::parse(&mut lexer, None) + .map_err(|error| anyhow::anyhow!("Yul object `{}` parsing error: {}", path, error))?; + + let mut project_contracts = BTreeMap::new(); + project_contracts.insert( + path.to_owned(), + Contract::new( + path, + source_hash, + source_version.clone(), + IR::new_yul(source_code.to_owned(), object), + None, + ), + ); + + Ok(Self::new( + source_version, + project_contracts, + BTreeMap::new(), + )) + } + + /// + /// Parses the LLVM IR source code file and returns the source data. + /// + pub fn try_from_llvm_ir_path(path: &Path) -> anyhow::Result { + let source_code = std::fs::read_to_string(path) + .map_err(|error| anyhow::anyhow!("LLVM IR file {:?} reading error: {}", path, error))?; + let source_hash = sha3::Keccak256::digest(source_code.as_bytes()).into(); + + let source_version = + SolcVersion::new_simple(era_compiler_llvm_context::eravm_const::LLVM_VERSION); + let path = path.to_string_lossy().to_string(); + + let mut project_contracts = BTreeMap::new(); + project_contracts.insert( + path.clone(), + Contract::new( + path.clone(), + source_hash, + source_version.clone(), + IR::new_llvm_ir(path, source_code), + None, + ), + ); + + Ok(Self::new( + source_version, + project_contracts, + BTreeMap::new(), + )) + } + + /// + /// Parses the EraVM assembly source code file and returns the source data. + /// + pub fn try_from_zkasm_path(path: &Path) -> anyhow::Result { + let source_code = std::fs::read_to_string(path).map_err(|error| { + anyhow::anyhow!("EraVM assembly file {:?} reading error: {}", path, error) + })?; + let source_hash = sha3::Keccak256::digest(source_code.as_bytes()).into(); + + let source_version = + SolcVersion::new_simple(era_compiler_llvm_context::eravm_const::ZKEVM_VERSION); + let path = path.to_string_lossy().to_string(); + + let mut project_contracts = BTreeMap::new(); + project_contracts.insert( + path.clone(), + Contract::new( + path.clone(), + source_hash, + source_version.clone(), + IR::new_zkasm(path, source_code), + None, + ), + ); + + Ok(Self::new( + source_version, + project_contracts, + BTreeMap::new(), + )) + } +} + +impl era_compiler_llvm_context::EraVMDependency for Project { + fn compile( + project: Self, + identifier: &str, + optimizer_settings: era_compiler_llvm_context::OptimizerSettings, + is_system_mode: bool, + include_metadata_hash: bool, + debug_config: Option, + ) -> anyhow::Result { + let contract_path = project.resolve_path(identifier)?; + let contract = project + .contracts + .get(contract_path.as_str()) + .cloned() + .ok_or_else(|| { + anyhow::anyhow!( + "Dependency contract `{}` not found in the project", + contract_path + ) + })?; + + contract + .compile( + project, + optimizer_settings, + is_system_mode, + include_metadata_hash, + debug_config, + ) + .map_err(|error| { + anyhow::anyhow!( + "Dependency contract `{}` compiling error: {}", + identifier, + error + ) + }) + .map(|contract| contract.build.bytecode_hash) + } + + fn resolve_path(&self, identifier: &str) -> anyhow::Result { + self.identifier_paths + .get(identifier.strip_suffix("_deployed").unwrap_or(identifier)) + .cloned() + .ok_or_else(|| { + anyhow::anyhow!( + "Contract with identifier `{}` not found in the project", + identifier + ) + }) + } + + fn resolve_library(&self, path: &str) -> anyhow::Result { + for (file_path, contracts) in self.libraries.iter() { + for (contract_name, address) in contracts.iter() { + let key = format!("{file_path}:{contract_name}"); + if key.as_str() == path { + return Ok(address["0x".len()..].to_owned()); + } + } + } + + anyhow::bail!("Library `{}` not found in the project", path); + } +} diff --git a/crates/solidity/src/solc/combined_json/contract.rs b/crates/solidity/src/solc/combined_json/contract.rs new file mode 100644 index 0000000..b1e9916 --- /dev/null +++ b/crates/solidity/src/solc/combined_json/contract.rs @@ -0,0 +1,79 @@ +//! +//! The `solc --combined-json` contract. +//! + +use std::collections::BTreeMap; +use std::collections::HashSet; + +use serde::Deserialize; +use serde::Serialize; + +/// +/// The contract. +/// +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "kebab-case")] +pub struct Contract { + /// The `solc` hashes output. + #[serde(skip_serializing_if = "Option::is_none")] + pub hashes: Option>, + /// The `solc` ABI output. + #[serde(skip_serializing_if = "Option::is_none")] + pub abi: Option, + /// The `solc` metadata output. + #[serde(skip_serializing_if = "Option::is_none")] + pub metadata: Option, + /// The `solc` developer documentation output. + #[serde(skip_serializing_if = "Option::is_none")] + pub devdoc: Option, + /// The `solc` user documentation output. + #[serde(skip_serializing_if = "Option::is_none")] + pub userdoc: Option, + /// The `solc` storage layout output. + #[serde(skip_serializing_if = "Option::is_none")] + pub storage_layout: Option, + /// The `solc` AST output. + #[serde(skip_serializing_if = "Option::is_none")] + pub ast: Option, + /// The `solc` assembly output. + #[serde(skip_serializing_if = "Option::is_none")] + pub asm: Option, + /// The `solc` hexadecimal binary output. + #[serde(skip_serializing_if = "Option::is_none")] + pub bin: Option, + /// The `solc` hexadecimal binary runtime part output. + #[serde(skip_serializing_if = "Option::is_none")] + pub bin_runtime: Option, + /// The factory dependencies. + #[serde(skip_serializing_if = "Option::is_none")] + pub factory_deps: Option>, + /// The missing libraries. + #[serde(skip_serializing_if = "Option::is_none")] + pub missing_libraries: Option>, +} + +impl Contract { + /// + /// Returns the signature hash of the specified contract entry. + /// + /// # Panics + /// If the hashes have not been requested in the `solc` call. + /// + pub fn entry(&self, entry: &str) -> u32 { + self.hashes + .as_ref() + .expect("Always exists") + .iter() + .find_map(|(contract_entry, hash)| { + if contract_entry.starts_with(entry) { + Some( + u32::from_str_radix(hash.as_str(), era_compiler_common::BASE_HEXADECIMAL) + .expect("Test hash is always valid"), + ) + } else { + None + } + }) + .unwrap_or_else(|| panic!("Entry `{entry}` not found")) + } +} diff --git a/crates/solidity/src/solc/combined_json/mod.rs b/crates/solidity/src/solc/combined_json/mod.rs new file mode 100644 index 0000000..b96305e --- /dev/null +++ b/crates/solidity/src/solc/combined_json/mod.rs @@ -0,0 +1,108 @@ +//! +//! The `solc --combined-json` output. +//! + +pub mod contract; + +use std::collections::BTreeMap; +use std::fs::File; +use std::io::Write; +use std::path::Path; + +use serde::Deserialize; +use serde::Serialize; + +use self::contract::Contract; + +/// +/// The `solc --combined-json` output. +/// +#[derive(Debug, Serialize, Deserialize)] +pub struct CombinedJson { + /// The contract entries. + pub contracts: BTreeMap, + /// The list of source files. + #[serde(rename = "sourceList")] + #[serde(skip_serializing_if = "Option::is_none")] + pub source_list: Option>, + /// The source code extra data, including the AST. + #[serde(skip_serializing_if = "Option::is_none")] + pub sources: Option, + /// The `solc` compiler version. + pub version: String, + /// The `zksolc` compiler version. + #[serde(skip_serializing_if = "Option::is_none")] + pub zk_version: Option, +} + +impl CombinedJson { + /// + /// Returns the signature hash of the specified contract and entry. + /// + pub fn entry(&self, path: &str, entry: &str) -> u32 { + self.contracts + .iter() + .find_map(|(name, contract)| { + if name.starts_with(path) { + Some(contract) + } else { + None + } + }) + .expect("Always exists") + .entry(entry) + } + + /// + /// Returns the full contract path which can be found in `combined-json` output. + /// + pub fn get_full_path(&self, name: &str) -> Option { + self.contracts.iter().find_map(|(path, _value)| { + if let Some(last_slash_position) = path.rfind('/') { + if let Some(colon_position) = path.rfind(':') { + if &path[last_slash_position + 1..colon_position] == name { + return Some(path.to_owned()); + } + } + } + + None + }) + } + + /// + /// Removes EVM artifacts to prevent their accidental usage. + /// + pub fn remove_evm(&mut self) { + for (_, contract) in self.contracts.iter_mut() { + contract.bin = None; + contract.bin_runtime = None; + } + } + + /// + /// Writes the JSON to the specified directory. + /// + pub fn write_to_directory( + self, + output_directory: &Path, + overwrite: bool, + ) -> anyhow::Result<()> { + let mut file_path = output_directory.to_owned(); + file_path.push(format!("combined.{}", era_compiler_common::EXTENSION_JSON)); + + if file_path.exists() && !overwrite { + eprintln!( + "Refusing to overwrite an existing file {file_path:?} (use --overwrite to force)." + ); + return Ok(()); + } + + File::create(&file_path) + .map_err(|error| anyhow::anyhow!("File {:?} creating error: {}", file_path, error))? + .write_all(serde_json::to_vec(&self).expect("Always valid").as_slice()) + .map_err(|error| anyhow::anyhow!("File {:?} writing error: {}", file_path, error))?; + + Ok(()) + } +} diff --git a/crates/solidity/src/solc/mod.rs b/crates/solidity/src/solc/mod.rs new file mode 100644 index 0000000..fb69551 --- /dev/null +++ b/crates/solidity/src/solc/mod.rs @@ -0,0 +1,315 @@ +//! +//! The Solidity compiler. +//! + +pub mod combined_json; +pub mod pipeline; +pub mod standard_json; +pub mod version; + +use std::io::Write; +use std::path::Path; +use std::path::PathBuf; + +use self::combined_json::CombinedJson; +use self::pipeline::Pipeline; +use self::standard_json::input::Input as StandardJsonInput; +use self::standard_json::output::Output as StandardJsonOutput; +use self::version::Version; + +/// +/// The Solidity compiler. +/// +pub struct Compiler { + /// The binary executable name. + pub executable: String, + /// The lazily-initialized compiler version. + pub version: Option, +} + +impl Compiler { + /// The default executable name. + pub const DEFAULT_EXECUTABLE_NAME: &'static str = "solc"; + + /// The first version of `solc` with the support of standard JSON interface. + pub const FIRST_SUPPORTED_VERSION: semver::Version = semver::Version::new(0, 4, 12); + + /// The first version of `solc`, where Yul codegen is considered robust enough. + pub const FIRST_YUL_VERSION: semver::Version = semver::Version::new(0, 8, 0); + + /// The first version of `solc`, where `--via-ir` codegen mode is supported. + pub const FIRST_VIA_IR_VERSION: semver::Version = semver::Version::new(0, 8, 13); + + /// The last supported version of `solc`. + pub const LAST_SUPPORTED_VERSION: semver::Version = semver::Version::new(0, 8, 24); + + /// + /// A shortcut constructor. + /// + /// Different tools may use different `executable` names. For example, the integration tester + /// uses `solc-` format. + /// + pub fn new(executable: String) -> anyhow::Result { + if let Err(error) = which::which(executable.as_str()) { + anyhow::bail!( + "The `{executable}` executable not found in ${{PATH}}: {}", + error + ); + } + Ok(Self { + executable, + version: None, + }) + } + + /// + /// Compiles the Solidity `--standard-json` input into Yul IR. + /// + pub fn standard_json( + &mut self, + mut input: StandardJsonInput, + pipeline: Pipeline, + base_path: Option, + include_paths: Vec, + allow_paths: Option, + ) -> anyhow::Result { + let version = self.version()?; + + let mut command = std::process::Command::new(self.executable.as_str()); + command.stdin(std::process::Stdio::piped()); + command.stdout(std::process::Stdio::piped()); + command.arg("--standard-json"); + + if let Some(base_path) = base_path { + command.arg("--base-path"); + command.arg(base_path); + } + for include_path in include_paths.into_iter() { + command.arg("--include-path"); + command.arg(include_path); + } + if let Some(allow_paths) = allow_paths { + command.arg("--allow-paths"); + command.arg(allow_paths); + } + + input.normalize(&version.default); + + let suppressed_warnings = input.suppressed_warnings.take().unwrap_or_default(); + + let input_json = serde_json::to_vec(&input).expect("Always valid"); + + let process = command.spawn().map_err(|error| { + anyhow::anyhow!("{} subprocess spawning error: {:?}", self.executable, error) + })?; + process + .stdin + .as_ref() + .ok_or_else(|| anyhow::anyhow!("{} stdin getting error", self.executable))? + .write_all(input_json.as_slice()) + .map_err(|error| { + anyhow::anyhow!("{} stdin writing error: {:?}", self.executable, error) + })?; + + let output = process.wait_with_output().map_err(|error| { + anyhow::anyhow!("{} subprocess output error: {:?}", self.executable, error) + })?; + if !output.status.success() { + anyhow::bail!( + "{} error: {}", + self.executable, + String::from_utf8_lossy(output.stderr.as_slice()).to_string() + ); + } + + let mut output: StandardJsonOutput = era_compiler_common::deserialize_from_slice( + output.stdout.as_slice(), + ) + .map_err(|error| { + anyhow::anyhow!( + "{} subprocess output parsing error: {}\n{}", + self.executable, + error, + era_compiler_common::deserialize_from_slice::( + output.stdout.as_slice() + ) + .map(|json| serde_json::to_string_pretty(&json).expect("Always valid")) + .unwrap_or_else(|_| String::from_utf8_lossy(output.stdout.as_slice()).to_string()), + ) + })?; + output.preprocess_ast(&version, pipeline, suppressed_warnings.as_slice())?; + output.remove_evm(); + + Ok(output) + } + + /// + /// The `solc --combined-json abi,hashes...` mirror. + /// + pub fn combined_json( + &self, + paths: &[PathBuf], + combined_json_argument: &str, + ) -> anyhow::Result { + let mut command = std::process::Command::new(self.executable.as_str()); + command.args(paths); + + let mut combined_json_flags = Vec::new(); + let mut combined_json_fake_flag_pushed = false; + let mut filtered_flags = Vec::with_capacity(3); + for flag in combined_json_argument.split(',') { + match flag { + flag @ "asm" | flag @ "bin" | flag @ "bin-runtime" => filtered_flags.push(flag), + flag => combined_json_flags.push(flag), + } + } + if combined_json_flags.is_empty() { + combined_json_flags.push("ast"); + combined_json_fake_flag_pushed = true; + } + command.arg("--combined-json"); + command.arg(combined_json_flags.join(",")); + + let output = command.output().map_err(|error| { + anyhow::anyhow!("{} subprocess error: {:?}", self.executable, error) + })?; + if !output.status.success() { + println!("{}", String::from_utf8_lossy(output.stdout.as_slice())); + println!("{}", String::from_utf8_lossy(output.stderr.as_slice())); + anyhow::bail!( + "{} error: {}", + self.executable, + String::from_utf8_lossy(output.stdout.as_slice()).to_string() + ); + } + + let mut combined_json: CombinedJson = era_compiler_common::deserialize_from_slice( + output.stdout.as_slice(), + ) + .map_err(|error| { + anyhow::anyhow!( + "{} subprocess output parsing error: {}\n{}", + self.executable, + error, + era_compiler_common::deserialize_from_slice::( + output.stdout.as_slice() + ) + .map(|json| serde_json::to_string_pretty(&json).expect("Always valid")) + .unwrap_or_else(|_| String::from_utf8_lossy(output.stdout.as_slice()).to_string()), + ) + })?; + for filtered_flag in filtered_flags.into_iter() { + for (_path, contract) in combined_json.contracts.iter_mut() { + match filtered_flag { + "asm" => contract.asm = Some(serde_json::Value::Null), + "bin" => contract.bin = Some("".to_owned()), + "bin-runtime" => contract.bin_runtime = Some("".to_owned()), + _ => continue, + } + } + } + if combined_json_fake_flag_pushed { + combined_json.source_list = None; + combined_json.sources = None; + } + combined_json.remove_evm(); + + Ok(combined_json) + } + + /// + /// The `solc` Yul validator. + /// + pub fn validate_yul(&self, path: &Path) -> anyhow::Result<()> { + let mut command = std::process::Command::new(self.executable.as_str()); + command.arg("--strict-assembly"); + command.arg(path); + + let output = command.output().map_err(|error| { + anyhow::anyhow!("{} subprocess error: {:?}", self.executable, error) + })?; + if !output.status.success() { + anyhow::bail!( + "{} error: {}", + self.executable, + String::from_utf8_lossy(output.stderr.as_slice()).to_string() + ); + } + + Ok(()) + } + + /// + /// The `solc --version` mini-parser. + /// + pub fn version(&mut self) -> anyhow::Result { + if let Some(version) = self.version.as_ref() { + return Ok(version.to_owned()); + } + + let mut command = std::process::Command::new(self.executable.as_str()); + command.arg("--version"); + let output = command.output().map_err(|error| { + anyhow::anyhow!("{} subprocess error: {:?}", self.executable, error) + })?; + if !output.status.success() { + anyhow::bail!( + "{} error: {}", + self.executable, + String::from_utf8_lossy(output.stderr.as_slice()).to_string() + ); + } + + let stdout = String::from_utf8_lossy(output.stdout.as_slice()); + let long = stdout + .lines() + .nth(1) + .ok_or_else(|| { + anyhow::anyhow!("{} version parsing: not enough lines", self.executable) + })? + .split(' ') + .nth(1) + .ok_or_else(|| { + anyhow::anyhow!( + "{} version parsing: not enough words in the 2nd line", + self.executable + ) + })? + .to_owned(); + let default: semver::Version = long + .split('+') + .next() + .ok_or_else(|| { + anyhow::anyhow!("{} version parsing: metadata dropping", self.executable) + })? + .parse() + .map_err(|error| anyhow::anyhow!("{} version parsing: {}", self.executable, error))?; + + let l2_revision: Option = stdout + .lines() + .nth(2) + .and_then(|line| line.split(' ').nth(1)) + .and_then(|line| line.split('-').nth(1)) + .and_then(|version| version.parse().ok()); + + let version = Version::new(long, default, l2_revision); + if version.default < Self::FIRST_SUPPORTED_VERSION { + anyhow::bail!( + "`solc` versions <{} are not supported, found {}", + Self::FIRST_SUPPORTED_VERSION, + version.default + ); + } + if version.default > Self::LAST_SUPPORTED_VERSION { + anyhow::bail!( + "`solc` versions >{} are not supported, found {}", + Self::LAST_SUPPORTED_VERSION, + version.default + ); + } + + self.version = Some(version.clone()); + + Ok(version) + } +} diff --git a/crates/solidity/src/solc/pipeline.rs b/crates/solidity/src/solc/pipeline.rs new file mode 100644 index 0000000..4d1e1a1 --- /dev/null +++ b/crates/solidity/src/solc/pipeline.rs @@ -0,0 +1,32 @@ +//! +//! The Solidity compiler pipeline type. +//! + +use crate::solc::version::Version as SolcVersion; +use crate::solc::Compiler as SolcCompiler; + +/// +/// The Solidity compiler pipeline type. +/// +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[allow(non_camel_case_types)] +#[allow(clippy::upper_case_acronyms)] +pub enum Pipeline { + /// The Yul IR. + Yul, + /// The EVM legacy assembly IR. + EVMLA, +} + +impl Pipeline { + /// + /// We always use EVMLA for Solidity <=0.7, or if the user does not want to compile via Yul. + /// + pub fn new(solc_version: &SolcVersion, force_evmla: bool) -> Self { + if solc_version.default < SolcCompiler::FIRST_YUL_VERSION || force_evmla { + Self::EVMLA + } else { + Self::Yul + } + } +} diff --git a/crates/solidity/src/solc/standard_json/input/language.rs b/crates/solidity/src/solc/standard_json/input/language.rs new file mode 100644 index 0000000..ae8a026 --- /dev/null +++ b/crates/solidity/src/solc/standard_json/input/language.rs @@ -0,0 +1,26 @@ +//! +//! The `solc --standard-json` input language. +//! + +use serde::Deserialize; +use serde::Serialize; + +/// +/// The `solc --standard-json` input language. +/// +#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Hash)] +pub enum Language { + /// The Solidity language. + Solidity, + /// The Yul IR. + Yul, +} + +impl std::fmt::Display for Language { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Solidity => write!(f, "Solidity"), + Self::Yul => write!(f, "Yul"), + } + } +} diff --git a/crates/solidity/src/solc/standard_json/input/mod.rs b/crates/solidity/src/solc/standard_json/input/mod.rs new file mode 100644 index 0000000..9e1512c --- /dev/null +++ b/crates/solidity/src/solc/standard_json/input/mod.rs @@ -0,0 +1,147 @@ +//! +//! The `solc --standard-json` input. +//! + +pub mod language; +pub mod settings; +pub mod source; + +use std::collections::BTreeMap; +use std::collections::BTreeSet; +use std::path::PathBuf; + +use rayon::iter::IntoParallelIterator; +use rayon::iter::ParallelIterator; +use serde::Deserialize; +use serde::Serialize; + +use crate::solc::pipeline::Pipeline as SolcPipeline; +use crate::solc::standard_json::input::settings::metadata::Metadata as SolcStandardJsonInputSettingsMetadata; +use crate::solc::standard_json::input::settings::optimizer::Optimizer as SolcStandardJsonInputSettingsOptimizer; +use crate::solc::standard_json::input::settings::selection::Selection as SolcStandardJsonInputSettingsSelection; +use crate::warning::Warning; + +use self::language::Language; +use self::settings::Settings; +use self::source::Source; + +/// +/// The `solc --standard-json` input. +/// +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Input { + /// The input language. + pub language: Language, + /// The input source code files hashmap. + pub sources: BTreeMap, + /// The compiler settings. + pub settings: Settings, + /// The suppressed warnings. + #[serde(skip_serializing)] + pub suppressed_warnings: Option>, +} + +impl Input { + /// + /// A shortcut constructor from stdin. + /// + pub fn try_from_stdin(solc_pipeline: SolcPipeline) -> anyhow::Result { + let mut input: Self = serde_json::from_reader(std::io::BufReader::new(std::io::stdin()))?; + input + .settings + .output_selection + .get_or_insert_with(SolcStandardJsonInputSettingsSelection::default) + .extend_with_required(solc_pipeline); + Ok(input) + } + + /// + /// A shortcut constructor from paths. + /// + #[allow(clippy::too_many_arguments)] + pub fn try_from_paths( + language: Language, + evm_version: Option, + paths: &[PathBuf], + library_map: Vec, + remappings: Option>, + output_selection: SolcStandardJsonInputSettingsSelection, + optimizer: SolcStandardJsonInputSettingsOptimizer, + metadata: Option, + via_ir: bool, + suppressed_warnings: Option>, + ) -> anyhow::Result { + let sources = paths + .into_par_iter() + .map(|path| { + let source = Source::try_from(path.as_path()).unwrap_or_else(|error| { + panic!("Source code file {path:?} reading error: {error}") + }); + (path.to_string_lossy().to_string(), source) + }) + .collect(); + + let libraries = Settings::parse_libraries(library_map)?; + + Ok(Self { + language, + sources, + settings: Settings::new( + evm_version, + libraries, + remappings, + output_selection, + via_ir, + optimizer, + metadata, + ), + suppressed_warnings, + }) + } + + /// + /// A shortcut constructor from source code. + /// + /// Only for the integration test purposes. + /// + #[allow(clippy::too_many_arguments)] + pub fn try_from_sources( + evm_version: Option, + sources: BTreeMap, + libraries: BTreeMap>, + remappings: Option>, + output_selection: SolcStandardJsonInputSettingsSelection, + optimizer: SolcStandardJsonInputSettingsOptimizer, + metadata: Option, + via_ir: bool, + suppressed_warnings: Option>, + ) -> anyhow::Result { + let sources = sources + .into_par_iter() + .map(|(path, content)| (path, Source::from(content))) + .collect(); + + Ok(Self { + language: Language::Solidity, + sources, + settings: Settings::new( + evm_version, + libraries, + remappings, + output_selection, + via_ir, + optimizer, + metadata, + ), + suppressed_warnings, + }) + } + + /// + /// Sets the necessary defaults. + /// + pub fn normalize(&mut self, version: &semver::Version) { + self.settings.normalize(version); + } +} diff --git a/crates/solidity/src/solc/standard_json/input/settings/metadata.rs b/crates/solidity/src/solc/standard_json/input/settings/metadata.rs new file mode 100644 index 0000000..54ac5a0 --- /dev/null +++ b/crates/solidity/src/solc/standard_json/input/settings/metadata.rs @@ -0,0 +1,28 @@ +//! +//! The `solc --standard-json` input settings metadata. +//! + +use serde::Deserialize; +use serde::Serialize; + +/// +/// The `solc --standard-json` input settings metadata. +/// +#[derive(Debug, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Metadata { + /// The bytecode hash mode. + #[serde(skip_serializing_if = "Option::is_none")] + pub bytecode_hash: Option, +} + +impl Metadata { + /// + /// A shortcut constructor. + /// + pub fn new(bytecode_hash: era_compiler_llvm_context::EraVMMetadataHash) -> Self { + Self { + bytecode_hash: Some(bytecode_hash), + } + } +} diff --git a/crates/solidity/src/solc/standard_json/input/settings/mod.rs b/crates/solidity/src/solc/standard_json/input/settings/mod.rs new file mode 100644 index 0000000..75e43da --- /dev/null +++ b/crates/solidity/src/solc/standard_json/input/settings/mod.rs @@ -0,0 +1,111 @@ +//! +//! The `solc --standard-json` input settings. +//! + +pub mod metadata; +pub mod optimizer; +pub mod selection; + +use std::collections::BTreeMap; +use std::collections::BTreeSet; + +use serde::Deserialize; +use serde::Serialize; + +use self::metadata::Metadata; +use self::optimizer::Optimizer; +use self::selection::Selection; + +/// +/// The `solc --standard-json` input settings. +/// +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Settings { + /// The target EVM version. + #[serde(skip_serializing_if = "Option::is_none")] + pub evm_version: Option, + /// The linker library addresses. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub libraries: Option>>, + /// The sorted list of remappings. + #[serde(skip_serializing_if = "Option::is_none")] + pub remappings: Option>, + /// The output selection filters. + #[serde(skip_serializing_if = "Option::is_none")] + pub output_selection: Option, + /// Whether to compile via IR. Only for testing with solc >=0.8.13. + #[serde( + rename = "viaIR", + skip_serializing_if = "Option::is_none", + skip_deserializing + )] + pub via_ir: Option, + /// The optimizer settings. + pub optimizer: Optimizer, + /// The metadata settings. + #[serde(skip_serializing_if = "Option::is_none")] + pub metadata: Option, +} + +impl Settings { + /// + /// A shortcut constructor. + /// + pub fn new( + evm_version: Option, + libraries: BTreeMap>, + remappings: Option>, + output_selection: Selection, + via_ir: bool, + optimizer: Optimizer, + metadata: Option, + ) -> Self { + Self { + evm_version, + libraries: Some(libraries), + remappings, + output_selection: Some(output_selection), + via_ir: if via_ir { Some(true) } else { None }, + optimizer, + metadata, + } + } + + /// + /// Sets the necessary defaults. + /// + pub fn normalize(&mut self, version: &semver::Version) { + self.optimizer.normalize(version); + } + + /// + /// Parses the library list and returns their double hashmap with path and name as keys. + /// + pub fn parse_libraries( + input: Vec, + ) -> anyhow::Result>> { + let mut libraries = BTreeMap::new(); + for (index, library) in input.into_iter().enumerate() { + let mut path_and_address = library.split('='); + let path = path_and_address + .next() + .ok_or_else(|| anyhow::anyhow!("The library #{} path is missing", index))?; + let mut file_and_contract = path.split(':'); + let file = file_and_contract + .next() + .ok_or_else(|| anyhow::anyhow!("The library `{}` file name is missing", path))?; + let contract = file_and_contract.next().ok_or_else(|| { + anyhow::anyhow!("The library `{}` contract name is missing", path) + })?; + let address = path_and_address + .next() + .ok_or_else(|| anyhow::anyhow!("The library `{}` address is missing", path))?; + libraries + .entry(file.to_owned()) + .or_insert_with(BTreeMap::new) + .insert(contract.to_owned(), address.to_owned()); + } + Ok(libraries) + } +} diff --git a/crates/solidity/src/solc/standard_json/input/settings/optimizer/details.rs b/crates/solidity/src/solc/standard_json/input/settings/optimizer/details.rs new file mode 100644 index 0000000..c397698 --- /dev/null +++ b/crates/solidity/src/solc/standard_json/input/settings/optimizer/details.rs @@ -0,0 +1,67 @@ +//! +//! The `solc --standard-json` input settings optimizer details. +//! + +use serde::Deserialize; +use serde::Serialize; + +/// +/// The `solc --standard-json` input settings optimizer details. +/// +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Details { + /// Whether the pass is enabled. + pub peephole: bool, + /// Whether the pass is enabled. + #[serde(skip_serializing_if = "Option::is_none")] + pub inliner: Option, + /// Whether the pass is enabled. + pub jumpdest_remover: bool, + /// Whether the pass is enabled. + pub order_literals: bool, + /// Whether the pass is enabled. + pub deduplicate: bool, + /// Whether the pass is enabled. + pub cse: bool, + /// Whether the pass is enabled. + pub constant_optimizer: bool, +} + +impl Details { + /// + /// A shortcut constructor. + /// + pub fn new( + peephole: bool, + inliner: Option, + jumpdest_remover: bool, + order_literals: bool, + deduplicate: bool, + cse: bool, + constant_optimizer: bool, + ) -> Self { + Self { + peephole, + inliner, + jumpdest_remover, + order_literals, + deduplicate, + cse, + constant_optimizer, + } + } + + /// + /// Creates a set of disabled optimizations. + /// + pub fn disabled(version: &semver::Version) -> Self { + let inliner = if version >= &semver::Version::new(0, 8, 5) { + Some(false) + } else { + None + }; + + Self::new(false, inliner, false, false, false, false, false) + } +} diff --git a/crates/solidity/src/solc/standard_json/input/settings/optimizer/mod.rs b/crates/solidity/src/solc/standard_json/input/settings/optimizer/mod.rs new file mode 100644 index 0000000..9575f29 --- /dev/null +++ b/crates/solidity/src/solc/standard_json/input/settings/optimizer/mod.rs @@ -0,0 +1,82 @@ +//! +//! The `solc --standard-json` input settings optimizer. +//! + +pub mod details; + +use serde::Deserialize; +use serde::Serialize; + +use self::details::Details; + +/// +/// The `solc --standard-json` input settings optimizer. +/// +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Optimizer { + /// Whether the optimizer is enabled. + pub enabled: bool, + /// The optimization mode string. + #[serde(skip_serializing)] + pub mode: Option, + /// The `solc` optimizer details. + #[serde(skip_serializing_if = "Option::is_none")] + pub details: Option
, + /// Whether to try to recompile with -Oz if the bytecode is too large. + #[serde(skip_serializing)] + pub fallback_to_optimizing_for_size: Option, + /// Whether to disable the system request memoization. + #[serde(skip_serializing)] + pub disable_system_request_memoization: Option, +} + +impl Optimizer { + /// + /// A shortcut constructor. + /// + pub fn new( + enabled: bool, + mode: Option, + version: &semver::Version, + fallback_to_optimizing_for_size: bool, + disable_system_request_memoization: bool, + ) -> Self { + Self { + enabled, + mode, + details: Some(Details::disabled(version)), + fallback_to_optimizing_for_size: Some(fallback_to_optimizing_for_size), + disable_system_request_memoization: Some(disable_system_request_memoization), + } + } + + /// + /// Sets the necessary defaults. + /// + pub fn normalize(&mut self, version: &semver::Version) { + self.details = if version >= &semver::Version::new(0, 5, 5) { + Some(Details::disabled(version)) + } else { + None + }; + } +} + +impl TryFrom<&Optimizer> for era_compiler_llvm_context::OptimizerSettings { + type Error = anyhow::Error; + + fn try_from(value: &Optimizer) -> Result { + let mut result = match value.mode { + Some(mode) => Self::try_from_cli(mode)?, + None => Self::cycles(), + }; + if value.fallback_to_optimizing_for_size.unwrap_or_default() { + result.enable_fallback_to_size(); + } + if value.disable_system_request_memoization.unwrap_or_default() { + result.disable_system_request_memoization(); + } + Ok(result) + } +} diff --git a/crates/solidity/src/solc/standard_json/input/settings/selection/file/flag.rs b/crates/solidity/src/solc/standard_json/input/settings/selection/file/flag.rs new file mode 100644 index 0000000..2f57e50 --- /dev/null +++ b/crates/solidity/src/solc/standard_json/input/settings/selection/file/flag.rs @@ -0,0 +1,69 @@ +//! +//! The `solc --standard-json` expected output selection flag. +//! + +use serde::Deserialize; +use serde::Serialize; + +use crate::solc::pipeline::Pipeline as SolcPipeline; + +/// +/// The `solc --standard-json` expected output selection flag. +/// +#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Hash)] +#[allow(non_camel_case_types)] +#[allow(clippy::upper_case_acronyms)] +pub enum Flag { + /// The ABI JSON. + #[serde(rename = "abi")] + ABI, + /// The metadata. + #[serde(rename = "metadata")] + Metadata, + /// The developer documentation. + #[serde(rename = "devdoc")] + Devdoc, + /// The user documentation. + #[serde(rename = "userdoc")] + Userdoc, + /// The function signature hashes JSON. + #[serde(rename = "evm.methodIdentifiers")] + MethodIdentifiers, + /// The storage layout. + #[serde(rename = "storageLayout")] + StorageLayout, + /// The AST JSON. + #[serde(rename = "ast")] + AST, + /// The Yul IR. + #[serde(rename = "irOptimized")] + Yul, + /// The EVM legacy assembly JSON. + #[serde(rename = "evm.legacyAssembly")] + EVMLA, +} + +impl From for Flag { + fn from(pipeline: SolcPipeline) -> Self { + match pipeline { + SolcPipeline::Yul => Self::Yul, + SolcPipeline::EVMLA => Self::EVMLA, + } + } +} + +impl std::fmt::Display for Flag { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::ABI => write!(f, "abi"), + Self::Metadata => write!(f, "metadata"), + Self::Devdoc => write!(f, "devdoc"), + Self::Userdoc => write!(f, "userdoc"), + Self::MethodIdentifiers => write!(f, "evm.methodIdentifiers"), + Self::StorageLayout => write!(f, "storageLayout"), + Self::AST => write!(f, "ast"), + Self::Yul => write!(f, "irOptimized"), + Self::EVMLA => write!(f, "evm.legacyAssembly"), + } + } +} diff --git a/crates/solidity/src/solc/standard_json/input/settings/selection/file/mod.rs b/crates/solidity/src/solc/standard_json/input/settings/selection/file/mod.rs new file mode 100644 index 0000000..da4f35a --- /dev/null +++ b/crates/solidity/src/solc/standard_json/input/settings/selection/file/mod.rs @@ -0,0 +1,58 @@ +//! +//! The `solc --standard-json` output file selection. +//! + +pub mod flag; + +use std::collections::HashSet; + +use serde::Deserialize; +use serde::Serialize; + +use crate::solc::pipeline::Pipeline as SolcPipeline; + +use self::flag::Flag as SelectionFlag; + +/// +/// The `solc --standard-json` output file selection. +/// +#[derive(Debug, Default, Serialize, Deserialize)] +pub struct File { + /// The per-file output selections. + #[serde(rename = "", skip_serializing_if = "Option::is_none")] + pub per_file: Option>, + /// The per-contract output selections. + #[serde(rename = "*", skip_serializing_if = "Option::is_none")] + pub per_contract: Option>, +} + +impl File { + /// + /// Creates the selection required by our compilation process. + /// + pub fn new_required(pipeline: SolcPipeline) -> Self { + Self { + per_file: Some(HashSet::from_iter([SelectionFlag::AST])), + per_contract: Some(HashSet::from_iter([ + SelectionFlag::MethodIdentifiers, + SelectionFlag::Metadata, + SelectionFlag::from(pipeline), + ])), + } + } + + /// + /// Extends the user's output selection with flag required by our compilation process. + /// + pub fn extend_with_required(&mut self, pipeline: SolcPipeline) -> &mut Self { + let required = Self::new_required(pipeline); + + self.per_file + .get_or_insert_with(HashSet::default) + .extend(required.per_file.unwrap_or_default()); + self.per_contract + .get_or_insert_with(HashSet::default) + .extend(required.per_contract.unwrap_or_default()); + self + } +} diff --git a/crates/solidity/src/solc/standard_json/input/settings/selection/mod.rs b/crates/solidity/src/solc/standard_json/input/settings/selection/mod.rs new file mode 100644 index 0000000..8694806 --- /dev/null +++ b/crates/solidity/src/solc/standard_json/input/settings/selection/mod.rs @@ -0,0 +1,43 @@ +//! +//! The `solc --standard-json` output selection. +//! + +pub mod file; + +use serde::Deserialize; +use serde::Serialize; + +use crate::solc::pipeline::Pipeline as SolcPipeline; + +use self::file::File as FileSelection; + +/// +/// The `solc --standard-json` output selection. +/// +#[derive(Debug, Default, Serialize, Deserialize)] +pub struct Selection { + /// Only the 'all' wildcard is available for robustness reasons. + #[serde(rename = "*", skip_serializing_if = "Option::is_none")] + pub all: Option, +} + +impl Selection { + /// + /// Creates the selection required by our compilation process. + /// + pub fn new_required(pipeline: SolcPipeline) -> Self { + Self { + all: Some(FileSelection::new_required(pipeline)), + } + } + + /// + /// Extends the user's output selection with flag required by our compilation process. + /// + pub fn extend_with_required(&mut self, pipeline: SolcPipeline) -> &mut Self { + self.all + .get_or_insert_with(|| FileSelection::new_required(pipeline)) + .extend_with_required(pipeline); + self + } +} diff --git a/crates/solidity/src/solc/standard_json/input/source.rs b/crates/solidity/src/solc/standard_json/input/source.rs new file mode 100644 index 0000000..fc2ec38 --- /dev/null +++ b/crates/solidity/src/solc/standard_json/input/source.rs @@ -0,0 +1,44 @@ +//! +//! The `solc --standard-json` input source. +//! + +use std::io::Read; +use std::path::Path; + +use serde::Deserialize; +use serde::Serialize; + +/// +/// The `solc --standard-json` input source. +/// +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Source { + /// The source code file content. + pub content: String, +} + +impl From for Source { + fn from(content: String) -> Self { + Self { content } + } +} + +impl TryFrom<&Path> for Source { + type Error = anyhow::Error; + + fn try_from(path: &Path) -> Result { + let content = if path.to_string_lossy() == "-" { + let mut solidity_code = String::with_capacity(16384); + std::io::stdin() + .read_to_string(&mut solidity_code) + .map_err(|error| anyhow::anyhow!(" reading error: {}", error))?; + solidity_code + } else { + std::fs::read_to_string(path) + .map_err(|error| anyhow::anyhow!("File {:?} reading error: {}", path, error))? + }; + + Ok(Self { content }) + } +} diff --git a/crates/solidity/src/solc/standard_json/mod.rs b/crates/solidity/src/solc/standard_json/mod.rs new file mode 100644 index 0000000..f97af81 --- /dev/null +++ b/crates/solidity/src/solc/standard_json/mod.rs @@ -0,0 +1,6 @@ +//! +//! The `solc .sol --standard-json`. +//! + +pub mod input; +pub mod output; diff --git a/crates/solidity/src/solc/standard_json/output/contract/evm/bytecode.rs b/crates/solidity/src/solc/standard_json/output/contract/evm/bytecode.rs new file mode 100644 index 0000000..eababa4 --- /dev/null +++ b/crates/solidity/src/solc/standard_json/output/contract/evm/bytecode.rs @@ -0,0 +1,25 @@ +//! +//! The `solc --standard-json` output contract EVM bytecode. +//! + +use serde::Deserialize; +use serde::Serialize; + +/// +/// The `solc --standard-json` output contract EVM bytecode. +/// +#[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(rename_all = "camelCase")] +pub struct Bytecode { + /// The bytecode object. + pub object: String, +} + +impl Bytecode { + /// + /// A shortcut constructor. + /// + pub fn new(object: String) -> Self { + Self { object } + } +} diff --git a/crates/solidity/src/solc/standard_json/output/contract/evm/extra_metadata/mod.rs b/crates/solidity/src/solc/standard_json/output/contract/evm/extra_metadata/mod.rs new file mode 100644 index 0000000..8bb4d74 --- /dev/null +++ b/crates/solidity/src/solc/standard_json/output/contract/evm/extra_metadata/mod.rs @@ -0,0 +1,52 @@ +//! +//! The `solc --standard-json` output contract EVM extra metadata. +//! + +pub mod recursive_function; + +use serde::Deserialize; +use serde::Serialize; + +use self::recursive_function::RecursiveFunction; + +/// +/// The `solc --standard-json` output contract EVM extra metadata. +/// +#[derive(Debug, Default, Serialize, Deserialize, Clone)] +#[serde(rename_all = "camelCase")] +pub struct ExtraMetadata { + /// The list of recursive functions. + #[serde(default = "Vec::new")] + pub recursive_functions: Vec, +} + +impl ExtraMetadata { + /// + /// Returns the recursive function reference for the specified tag. + /// + pub fn get( + &self, + block_key: &era_compiler_llvm_context::EraVMFunctionBlockKey, + ) -> Option<&RecursiveFunction> { + for function in self.recursive_functions.iter() { + match block_key.code_type { + era_compiler_llvm_context::EraVMCodeType::Deploy => { + if let Some(creation_tag) = function.creation_tag { + if num::BigUint::from(creation_tag) == block_key.tag { + return Some(function); + } + } + } + era_compiler_llvm_context::EraVMCodeType::Runtime => { + if let Some(runtime_tag) = function.runtime_tag { + if num::BigUint::from(runtime_tag) == block_key.tag { + return Some(function); + } + } + } + } + } + + None + } +} diff --git a/crates/solidity/src/solc/standard_json/output/contract/evm/extra_metadata/recursive_function.rs b/crates/solidity/src/solc/standard_json/output/contract/evm/extra_metadata/recursive_function.rs new file mode 100644 index 0000000..35e3e39 --- /dev/null +++ b/crates/solidity/src/solc/standard_json/output/contract/evm/extra_metadata/recursive_function.rs @@ -0,0 +1,26 @@ +//! +//! The `solc --standard-json` output contract EVM recursive function. +//! + +use serde::Deserialize; +use serde::Serialize; + +/// +/// The `solc --standard-json` output contract EVM recursive function. +/// +#[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(rename_all = "camelCase")] +pub struct RecursiveFunction { + /// The function name. + pub name: String, + /// The creation code function block tag. + pub creation_tag: Option, + /// The runtime code function block tag. + pub runtime_tag: Option, + /// The number of input arguments. + #[serde(rename = "totalParamSize")] + pub input_size: usize, + /// The number of output arguments. + #[serde(rename = "totalRetParamSize")] + pub output_size: usize, +} diff --git a/crates/solidity/src/solc/standard_json/output/contract/evm/mod.rs b/crates/solidity/src/solc/standard_json/output/contract/evm/mod.rs new file mode 100644 index 0000000..062be28 --- /dev/null +++ b/crates/solidity/src/solc/standard_json/output/contract/evm/mod.rs @@ -0,0 +1,51 @@ +//! +//! The `solc --standard-json` output contract EVM data. +//! + +pub mod bytecode; +pub mod extra_metadata; + +use std::collections::BTreeMap; + +use serde::Deserialize; +use serde::Serialize; + +use crate::evmla::assembly::Assembly; + +use self::bytecode::Bytecode; +use self::extra_metadata::ExtraMetadata; + +/// +/// The `solc --standard-json` output contract EVM data. +/// +/// It is replaced by EraVM data after compiling. +/// +#[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(rename_all = "camelCase")] +pub struct EVM { + /// The contract EVM legacy assembly code. + #[serde(rename = "legacyAssembly")] + pub assembly: Option, + /// The contract EraVM assembly code. + #[serde(rename = "assembly")] + pub assembly_text: Option, + /// The contract bytecode. + /// Is reset by that of EraVM before yielding the compiled project artifacts. + pub bytecode: Option, + /// The contract function signatures. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub method_identifiers: Option>, + /// The extra EVMLA metadata. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub extra_metadata: Option, +} + +impl EVM { + /// + /// Sets the EraVM assembly and bytecode. + /// + pub fn modify(&mut self, assembly_text: String, bytecode: String) { + self.assembly_text = Some(assembly_text); + self.bytecode = Some(Bytecode::new(bytecode)); + } +} diff --git a/crates/solidity/src/solc/standard_json/output/contract/mod.rs b/crates/solidity/src/solc/standard_json/output/contract/mod.rs new file mode 100644 index 0000000..91ccba0 --- /dev/null +++ b/crates/solidity/src/solc/standard_json/output/contract/mod.rs @@ -0,0 +1,51 @@ +//! +//! The `solc --standard-json` output contract. +//! + +pub mod evm; + +use std::collections::BTreeMap; +use std::collections::HashSet; + +use serde::Deserialize; +use serde::Serialize; + +use self::evm::EVM; + +/// +/// The `solc --standard-json` output contract. +/// +#[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(rename_all = "camelCase")] +pub struct Contract { + /// The contract ABI. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub abi: Option, + /// The contract metadata. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub metadata: Option, + /// The contract developer documentation. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub devdoc: Option, + /// The contract user documentation. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub userdoc: Option, + /// The contract storage layout. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub storage_layout: Option, + /// Contract's bytecode and related objects + #[serde(default, skip_serializing_if = "Option::is_none")] + pub evm: Option, + /// The contract optimized IR code. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub ir_optimized: Option, + /// The contract EraVM bytecode hash. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub hash: Option, + /// The contract factory dependencies. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub factory_dependencies: Option>, + /// The contract missing libraries. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub missing_libraries: Option>, +} diff --git a/crates/solidity/src/solc/standard_json/output/error/mod.rs b/crates/solidity/src/solc/standard_json/output/error/mod.rs new file mode 100644 index 0000000..3496ddf --- /dev/null +++ b/crates/solidity/src/solc/standard_json/output/error/mod.rs @@ -0,0 +1,177 @@ +//! +//! The `solc --standard-json` output error. +//! + +pub mod source_location; + +use std::str::FromStr; + +use serde::Deserialize; +use serde::Serialize; + +use self::source_location::SourceLocation; + +/// +/// The `solc --standard-json` output error. +/// +#[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(rename_all = "camelCase")] +pub struct Error { + /// The component type. + pub component: String, + /// The error code. + pub error_code: Option, + /// The formatted error message. + pub formatted_message: String, + /// The non-formatted error message. + pub message: String, + /// The error severity. + pub severity: String, + /// The error location data. + pub source_location: Option, + /// The error type. + pub r#type: String, +} + +impl Error { + /// + /// Returns the `ecrecover` function usage warning. + /// + pub fn message_ecrecover(src: Option<&str>) -> Self { + let message = r#" +┌──────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Warning: It looks like you are using 'ecrecover' to validate a signature of a user account. │ +│ zkSync Era comes with native account abstraction support, therefore it is highly recommended NOT │ +│ to rely on the fact that the account has an ECDSA private key attached to it since accounts might│ +│ implement other signature schemes. │ +│ Read more about Account Abstraction at https://v2-docs.zksync.io/dev/developer-guides/aa.html │ +└──────────────────────────────────────────────────────────────────────────────────────────────────┘"# + .to_owned(); + + Self { + component: "general".to_owned(), + error_code: None, + formatted_message: message.clone(), + message, + severity: "warning".to_owned(), + source_location: src.map(SourceLocation::from_str).and_then(Result::ok), + r#type: "Warning".to_owned(), + } + } + + /// + /// Returns the `
`'s `send` and `transfer` methods usage error. + /// + pub fn message_send_and_transfer(src: Option<&str>) -> Self { + let message = r#" +┌──────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Warning: It looks like you are using '
.send/transfer()' without providing │ +│ the gas amount. Such calls will fail depending on the pubdata costs. │ +│ This might be a false positive if you are using an interface (like IERC20) instead of the │ +│ native Solidity `send/transfer`. │ +│ Please use 'payable(
).call{value: }("")' instead, but be careful with the reentrancy │ +│ attack. `send` and `transfer` send limited amount of gas that prevents reentrancy, whereas │ +│ `
.call{value: }` sends all gas to the callee. Learn more on │ +│ https://docs.soliditylang.org/en/latest/security-considerations.html#reentrancy │ +└──────────────────────────────────────────────────────────────────────────────────────────────────┘"# + .to_owned(); + + Self { + component: "general".to_owned(), + error_code: None, + formatted_message: message.clone(), + message, + severity: "warning".to_owned(), + source_location: src.map(SourceLocation::from_str).and_then(Result::ok), + r#type: "Warning".to_owned(), + } + } + + /// + /// Returns the `extcodesize` instruction usage warning. + /// + pub fn message_extcodesize(src: Option<&str>) -> Self { + let message = r#" +┌──────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Warning: Your code or one of its dependencies uses the 'extcodesize' instruction, which is │ +│ usually needed in the following cases: │ +│ 1. To detect whether an address belongs to a smart contract. │ +│ 2. To detect whether the deploy code execution has finished. │ +│ zkSync Era comes with native account abstraction support (so accounts are smart contracts, │ +│ including private-key controlled EOAs), and you should avoid differentiating between contracts │ +│ and non-contract addresses. │ +└──────────────────────────────────────────────────────────────────────────────────────────────────┘"# + .to_owned(); + + Self { + component: "general".to_owned(), + error_code: None, + formatted_message: message.clone(), + message, + severity: "warning".to_owned(), + source_location: src.map(SourceLocation::from_str).and_then(Result::ok), + r#type: "Warning".to_owned(), + } + } + + /// + /// Returns the `origin` instruction usage warning. + /// + pub fn message_tx_origin(src: Option<&str>) -> Self { + let message = r#" +┌──────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Warning: You are checking for 'tx.origin' in your code, which might lead to unexpected behavior. │ +│ zkSync Era comes with native account abstraction support, and therefore the initiator of a │ +│ transaction might be different from the contract calling your code. It is highly recommended NOT │ +│ to rely on tx.origin, but use msg.sender instead. │ +│ Read more about Account Abstraction at https://v2-docs.zksync.io/dev/developer-guides/aa.html │ +└──────────────────────────────────────────────────────────────────────────────────────────────────┘"# + .to_owned(); + + Self { + component: "general".to_owned(), + error_code: None, + formatted_message: message.clone(), + message, + severity: "warning".to_owned(), + source_location: src.map(SourceLocation::from_str).and_then(Result::ok), + r#type: "Warning".to_owned(), + } + } + + /// + /// Returns the internal function pointer usage error. + /// + pub fn message_internal_function_pointer(src: Option<&str>) -> Self { + let message = r#" +┌──────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Error: Internal function pointers are not supported in EVM legacy assembly pipeline. │ +│ Please use the Yul IR codegen instead. │ +└──────────────────────────────────────────────────────────────────────────────────────────────────┘"# + .to_owned(); + + Self { + component: "general".to_owned(), + error_code: None, + formatted_message: message.clone(), + message, + severity: "error".to_owned(), + source_location: src.map(SourceLocation::from_str).and_then(Result::ok), + r#type: "Error".to_owned(), + } + } + + /// + /// Appends the contract path to the message.. + /// + pub fn push_contract_path(&mut self, path: &str) { + self.formatted_message + .push_str(format!("\n--> {path}\n").as_str()); + } +} + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.formatted_message) + } +} diff --git a/crates/solidity/src/solc/standard_json/output/error/source_location.rs b/crates/solidity/src/solc/standard_json/output/error/source_location.rs new file mode 100644 index 0000000..ab4a572 --- /dev/null +++ b/crates/solidity/src/solc/standard_json/output/error/source_location.rs @@ -0,0 +1,47 @@ +//! +//! The `solc --standard-json` output error source location. +//! + +use std::str::FromStr; + +use serde::Deserialize; +use serde::Serialize; + +/// +/// The `solc --standard-json` output error source location. +/// +#[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(rename_all = "camelCase")] +pub struct SourceLocation { + /// The source file path. + pub file: String, + /// The start location. + pub start: isize, + /// The end location. + pub end: isize, +} + +impl FromStr for SourceLocation { + type Err = anyhow::Error; + + fn from_str(string: &str) -> Result { + let mut parts = string.split(':'); + let start = parts + .next() + .map(|string| string.parse::()) + .and_then(Result::ok) + .unwrap_or_default(); + let length = parts + .next() + .map(|string| string.parse::()) + .and_then(Result::ok) + .unwrap_or_default(); + let file = parts.next().unwrap_or_default().to_owned(); + + Ok(Self { + file, + start, + end: start + length, + }) + } +} diff --git a/crates/solidity/src/solc/standard_json/output/mod.rs b/crates/solidity/src/solc/standard_json/output/mod.rs new file mode 100644 index 0000000..bcc81ae --- /dev/null +++ b/crates/solidity/src/solc/standard_json/output/mod.rs @@ -0,0 +1,281 @@ +//! +//! The `solc --standard-json` output. +//! + +pub mod contract; +pub mod error; +pub mod source; + +use std::collections::BTreeMap; + +use serde::Deserialize; +use serde::Serialize; +use sha3::Digest; + +use crate::evmla::assembly::instruction::Instruction; +use crate::evmla::assembly::Assembly; +use crate::project::contract::ir::IR as ProjectContractIR; +use crate::project::contract::Contract as ProjectContract; +use crate::project::Project; +use crate::solc::pipeline::Pipeline as SolcPipeline; +use crate::solc::version::Version as SolcVersion; +use crate::warning::Warning; +use crate::yul::lexer::Lexer; +use crate::yul::parser::statement::object::Object; + +use self::contract::Contract; +use self::error::Error as SolcStandardJsonOutputError; +use self::source::Source; + +/// +/// The `solc --standard-json` output. +/// +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct Output { + /// The file-contract hashmap. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub contracts: Option>>, + /// The source code mapping data. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub sources: Option>, + /// The compilation errors and warnings. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub errors: Option>, + /// The `solc` compiler version. + #[serde(skip_serializing_if = "Option::is_none")] + pub version: Option, + /// The `solc` compiler long version. + #[serde(skip_serializing_if = "Option::is_none")] + pub long_version: Option, + /// The `zksolc` compiler version. + #[serde(skip_serializing_if = "Option::is_none")] + pub zk_version: Option, +} + +impl Output { + /// + /// Converts the `solc` JSON output into a convenient project. + /// + pub fn try_to_project( + &mut self, + source_code_files: BTreeMap, + libraries: BTreeMap>, + pipeline: SolcPipeline, + solc_version: &SolcVersion, + debug_config: Option<&era_compiler_llvm_context::DebugConfig>, + ) -> anyhow::Result { + if let SolcPipeline::EVMLA = pipeline { + self.preprocess_dependencies()?; + } + + let files = match self.contracts.as_ref() { + Some(files) => files, + None => { + anyhow::bail!( + "{}", + self.errors + .as_ref() + .map(|errors| serde_json::to_string_pretty(errors).expect("Always valid")) + .unwrap_or_else(|| "Unknown project assembling error".to_owned()) + ); + } + }; + let mut project_contracts = BTreeMap::new(); + + for (path, contracts) in files.iter() { + for (name, contract) in contracts.iter() { + let full_path = format!("{path}:{name}"); + + let source = match pipeline { + SolcPipeline::Yul => { + let ir_optimized = match contract.ir_optimized.to_owned() { + Some(ir_optimized) => ir_optimized, + None => continue, + }; + if ir_optimized.is_empty() { + continue; + } + + if let Some(debug_config) = debug_config { + debug_config.dump_yul(full_path.as_str(), ir_optimized.as_str())?; + } + + let mut lexer = Lexer::new(ir_optimized.to_owned()); + let object = Object::parse(&mut lexer, None).map_err(|error| { + anyhow::anyhow!("Contract `{}` parsing error: {:?}", full_path, error) + })?; + + ProjectContractIR::new_yul(ir_optimized.to_owned(), object) + } + SolcPipeline::EVMLA => { + let evm = contract.evm.as_ref(); + let assembly = match evm.and_then(|evm| evm.assembly.to_owned()) { + Some(assembly) => assembly.to_owned(), + None => continue, + }; + let extra_metadata = evm + .and_then(|evm| evm.extra_metadata.to_owned()) + .unwrap_or_default(); + + ProjectContractIR::new_evmla(assembly, extra_metadata) + } + }; + + let source_code = source_code_files + .get(path.as_str()) + .ok_or_else(|| anyhow::anyhow!("Source code for path `{}` not found", path))?; + let source_hash = sha3::Keccak256::digest(source_code.as_bytes()).into(); + + let project_contract = ProjectContract::new( + full_path.clone(), + source_hash, + solc_version.to_owned(), + source, + contract.metadata.to_owned(), + ); + project_contracts.insert(full_path, project_contract); + } + } + + Ok(Project::new( + solc_version.to_owned(), + project_contracts, + libraries, + )) + } + + /// + /// Removes EVM artifacts to prevent their accidental usage. + /// + pub fn remove_evm(&mut self) { + if let Some(files) = self.contracts.as_mut() { + for (_, file) in files.iter_mut() { + for (_, contract) in file.iter_mut() { + if let Some(evm) = contract.evm.as_mut() { + evm.bytecode = None; + } + } + } + } + } + + /// + /// Traverses the AST and returns the list of additional errors and warnings. + /// + pub fn preprocess_ast( + &mut self, + version: &SolcVersion, + pipeline: SolcPipeline, + suppressed_warnings: &[Warning], + ) -> anyhow::Result<()> { + let sources = match self.sources.as_ref() { + Some(sources) => sources, + None => return Ok(()), + }; + + let mut messages = Vec::new(); + for (path, source) in sources.iter() { + if let Some(ast) = source.ast.as_ref() { + let mut eravm_messages = + Source::get_messages(ast, version, pipeline, suppressed_warnings); + for message in eravm_messages.iter_mut() { + message.push_contract_path(path.as_str()); + } + messages.extend(eravm_messages); + } + } + + self.errors = match self.errors.take() { + Some(mut errors) => { + errors.extend(messages); + Some(errors) + } + None => Some(messages), + }; + + Ok(()) + } + + /// + /// The pass, which replaces with dependency indexes with actual data. + /// + fn preprocess_dependencies(&mut self) -> anyhow::Result<()> { + let files = match self.contracts.as_mut() { + Some(files) => files, + None => return Ok(()), + }; + let mut hash_path_mapping = BTreeMap::new(); + + for (path, contracts) in files.iter() { + for (name, contract) in contracts.iter() { + let full_path = format!("{path}:{name}"); + let hash = match contract + .evm + .as_ref() + .and_then(|evm| evm.assembly.as_ref()) + .map(|assembly| assembly.keccak256()) + { + Some(hash) => hash, + None => continue, + }; + + hash_path_mapping.insert(hash, full_path); + } + } + + for (path, contracts) in files.iter_mut() { + for (name, contract) in contracts.iter_mut() { + let assembly = match contract.evm.as_mut().and_then(|evm| evm.assembly.as_mut()) { + Some(assembly) => assembly, + None => continue, + }; + + let full_path = format!("{path}:{name}"); + Self::preprocess_dependency_level( + full_path.as_str(), + assembly, + &hash_path_mapping, + )?; + } + } + + Ok(()) + } + + /// + /// Preprocesses an assembly JSON structure dependency data map. + /// + fn preprocess_dependency_level( + full_path: &str, + assembly: &mut Assembly, + hash_path_mapping: &BTreeMap, + ) -> anyhow::Result<()> { + assembly.set_full_path(full_path.to_owned()); + + let deploy_code_index_path_mapping = + assembly.deploy_dependencies_pass(full_path, hash_path_mapping)?; + if let Some(deploy_code_instructions) = assembly.code.as_deref_mut() { + Instruction::replace_data_aliases( + deploy_code_instructions, + &deploy_code_index_path_mapping, + )?; + }; + + let runtime_code_index_path_mapping = + assembly.runtime_dependencies_pass(full_path, hash_path_mapping)?; + if let Some(runtime_code_instructions) = assembly + .data + .as_mut() + .and_then(|data_map| data_map.get_mut("0")) + .and_then(|data| data.get_assembly_mut()) + .and_then(|assembly| assembly.code.as_deref_mut()) + { + Instruction::replace_data_aliases( + runtime_code_instructions, + &runtime_code_index_path_mapping, + )?; + } + + Ok(()) + } +} diff --git a/crates/solidity/src/solc/standard_json/output/source.rs b/crates/solidity/src/solc/standard_json/output/source.rs new file mode 100644 index 0000000..9b0752c --- /dev/null +++ b/crates/solidity/src/solc/standard_json/output/source.rs @@ -0,0 +1,265 @@ +//! +//! The `solc --standard-json` output source. +//! + +use serde::Deserialize; +use serde::Serialize; + +use crate::solc::pipeline::Pipeline as SolcPipeline; +use crate::solc::standard_json::output::error::Error as SolcStandardJsonOutputError; +use crate::solc::version::Version as SolcVersion; +use crate::warning::Warning; + +/// +/// The `solc --standard-json` output source. +/// +#[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(rename_all = "camelCase")] +pub struct Source { + /// The source code ID. + pub id: usize, + /// The source code AST. + pub ast: Option, +} + +impl Source { + /// + /// Checks the AST node for the `ecrecover` function usage. + /// + pub fn check_ecrecover(ast: &serde_json::Value) -> Option { + let ast = ast.as_object()?; + + if ast.get("nodeType")?.as_str()? != "FunctionCall" { + return None; + } + + let expression = ast.get("expression")?.as_object()?; + if expression.get("nodeType")?.as_str()? != "Identifier" { + return None; + } + if expression.get("name")?.as_str()? != "ecrecover" { + return None; + } + + Some(SolcStandardJsonOutputError::message_ecrecover( + ast.get("src")?.as_str(), + )) + } + + /// + /// Checks the AST node for the `
`'s `send` and `transfer` methods usage. + /// + pub fn check_send_and_transfer(ast: &serde_json::Value) -> Option { + let ast = ast.as_object()?; + + if ast.get("nodeType")?.as_str()? != "FunctionCall" { + return None; + } + + let expression = ast.get("expression")?.as_object()?; + if expression.get("nodeType")?.as_str()? != "MemberAccess" { + return None; + } + let member_name = expression.get("memberName")?.as_str()?; + if member_name != "send" && member_name != "transfer" { + return None; + } + + Some(SolcStandardJsonOutputError::message_send_and_transfer( + ast.get("src")?.as_str(), + )) + } + + /// + /// Checks the AST node for the `extcodesize` assembly instruction usage. + /// + pub fn check_assembly_extcodesize( + ast: &serde_json::Value, + ) -> Option { + let ast = ast.as_object()?; + + if ast.get("nodeType")?.as_str()? != "YulFunctionCall" { + return None; + } + if ast + .get("functionName")? + .as_object()? + .get("name")? + .as_str()? + != "extcodesize" + { + return None; + } + + Some(SolcStandardJsonOutputError::message_extcodesize( + ast.get("src")?.as_str(), + )) + } + + /// + /// Checks the AST node for the `origin` assembly instruction usage. + /// + pub fn check_assembly_origin(ast: &serde_json::Value) -> Option { + let ast = ast.as_object()?; + + if ast.get("nodeType")?.as_str()? != "YulFunctionCall" { + return None; + } + if ast + .get("functionName")? + .as_object()? + .get("name")? + .as_str()? + != "origin" + { + return None; + } + + Some(SolcStandardJsonOutputError::message_tx_origin( + ast.get("src")?.as_str(), + )) + } + + /// + /// Checks the AST node for the `tx.origin` value usage. + /// + pub fn check_tx_origin(ast: &serde_json::Value) -> Option { + let ast = ast.as_object()?; + + if ast.get("nodeType")?.as_str()? != "MemberAccess" { + return None; + } + if ast.get("memberName")?.as_str()? != "origin" { + return None; + } + + let expression = ast.get("expression")?.as_object()?; + if expression.get("nodeType")?.as_str()? != "Identifier" { + return None; + } + if expression.get("name")?.as_str()? != "tx" { + return None; + } + + Some(SolcStandardJsonOutputError::message_tx_origin( + ast.get("src")?.as_str(), + )) + } + + /// + /// Checks the AST node for the internal function pointers value usage. + /// + pub fn check_internal_function_pointer( + ast: &serde_json::Value, + ) -> Option { + let ast = ast.as_object()?; + + if ast.get("nodeType")?.as_str()? != "VariableDeclaration" { + return None; + } + + let type_descriptions = ast.get("typeDescriptions")?.as_object()?; + if !type_descriptions + .get("typeIdentifier")? + .as_str()? + .contains("function_internal") + { + return None; + } + + Some( + SolcStandardJsonOutputError::message_internal_function_pointer( + ast.get("src")?.as_str(), + ), + ) + } + + /// + /// Returns the list of messages for some specific parts of the AST. + /// + pub fn get_messages( + ast: &serde_json::Value, + version: &SolcVersion, + pipeline: SolcPipeline, + suppressed_warnings: &[Warning], + ) -> Vec { + let mut messages = Vec::new(); + if !suppressed_warnings.contains(&Warning::EcRecover) { + if let Some(message) = Self::check_ecrecover(ast) { + messages.push(message); + } + } + if !suppressed_warnings.contains(&Warning::SendTransfer) { + if let Some(message) = Self::check_send_and_transfer(ast) { + messages.push(message); + } + } + if !suppressed_warnings.contains(&Warning::ExtCodeSize) { + if let Some(message) = Self::check_assembly_extcodesize(ast) { + messages.push(message); + } + } + if !suppressed_warnings.contains(&Warning::TxOrigin) { + if let Some(message) = Self::check_assembly_origin(ast) { + messages.push(message); + } + if let Some(message) = Self::check_tx_origin(ast) { + messages.push(message); + } + } + if SolcPipeline::EVMLA == pipeline && version.l2_revision.is_none() { + if let Some(message) = Self::check_internal_function_pointer(ast) { + messages.push(message); + } + } + + match ast { + serde_json::Value::Array(array) => { + for element in array.iter() { + messages.extend(Self::get_messages( + element, + version, + pipeline, + suppressed_warnings, + )); + } + } + serde_json::Value::Object(object) => { + for (_key, value) in object.iter() { + messages.extend(Self::get_messages( + value, + version, + pipeline, + suppressed_warnings, + )); + } + } + _ => {} + } + + messages + } + + /// + /// Returns the name of the last contract. + /// + pub fn last_contract_name(&self) -> anyhow::Result { + self.ast + .as_ref() + .ok_or_else(|| anyhow::anyhow!("The AST is empty"))? + .get("nodes") + .and_then(|value| value.as_array()) + .ok_or_else(|| { + anyhow::anyhow!("The last contract cannot be found in an empty list of nodes") + })? + .iter() + .filter_map( + |node| match node.get("nodeType").and_then(|node| node.as_str()) { + Some("ContractDefinition") => Some(node.get("name")?.as_str()?.to_owned()), + _ => None, + }, + ) + .last() + .ok_or_else(|| anyhow::anyhow!("The last contract not found in the AST")) + } +} diff --git a/crates/solidity/src/solc/version.rs b/crates/solidity/src/solc/version.rs new file mode 100644 index 0000000..460759b --- /dev/null +++ b/crates/solidity/src/solc/version.rs @@ -0,0 +1,47 @@ +//! +//! The Solidity compiler version. +//! + +use serde::Deserialize; +use serde::Serialize; + +/// +/// The Solidity compiler version. +/// +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct Version { + /// The long version string. + pub long: String, + /// The short `semver`. + pub default: semver::Version, + /// The L2 revision additional versioning. + pub l2_revision: Option, +} + +impl Version { + /// + /// A shortcut constructor. + /// + pub fn new( + long: String, + default: semver::Version, + l2_revision: Option, + ) -> Self { + Self { + long, + default, + l2_revision, + } + } + + /// + /// A shortcut constructor for a simple version. + /// + pub fn new_simple(version: semver::Version) -> Self { + Self { + long: version.to_string(), + default: version, + l2_revision: None, + } + } +} diff --git a/crates/solidity/src/test_utils.rs b/crates/solidity/src/test_utils.rs new file mode 100644 index 0000000..bf2bd24 --- /dev/null +++ b/crates/solidity/src/test_utils.rs @@ -0,0 +1,197 @@ +//! Common utility used for in frontend and integration tests. +use std::collections::BTreeMap; +use std::collections::BTreeSet; +use std::path::PathBuf; +use std::str::FromStr; + +use crate::project::Project; +use crate::solc::pipeline::Pipeline as SolcPipeline; +use crate::solc::standard_json::input::settings::optimizer::Optimizer as SolcStandardJsonInputSettingsOptimizer; +use crate::solc::standard_json::input::settings::selection::Selection as SolcStandardJsonInputSettingsSelection; +use crate::solc::standard_json::input::Input as SolcStandardJsonInput; +use crate::solc::standard_json::output::Output as SolcStandardJsonOutput; +use crate::solc::Compiler as SolcCompiler; +use crate::warning::Warning; + +/// +/// Checks if the required executables are present in `${PATH}`. +/// +fn check_dependencies() { + for executable in [ + crate::r#const::DEFAULT_EXECUTABLE_NAME, + SolcCompiler::DEFAULT_EXECUTABLE_NAME, + ] + .iter() + { + assert!( + which::which(executable).is_ok(), + "The `{executable}` executable not found in ${{PATH}}" + ); + } +} + +/// +/// Builds the Solidity project and returns the standard JSON output. +/// +pub fn build_solidity( + sources: BTreeMap, + libraries: BTreeMap>, + remappings: Option>, + pipeline: SolcPipeline, + optimizer_settings: era_compiler_llvm_context::OptimizerSettings, +) -> anyhow::Result { + check_dependencies(); + + inkwell::support::enable_llvm_pretty_stack_trace(); + era_compiler_llvm_context::initialize_target(era_compiler_llvm_context::Target::PVM); + let _ = crate::process::EXECUTABLE.set(PathBuf::from(crate::r#const::DEFAULT_EXECUTABLE_NAME)); + + let mut solc = SolcCompiler::new(SolcCompiler::DEFAULT_EXECUTABLE_NAME.to_owned())?; + let solc_version = solc.version()?; + + let input = SolcStandardJsonInput::try_from_sources( + None, + sources.clone(), + libraries.clone(), + remappings, + SolcStandardJsonInputSettingsSelection::new_required(pipeline), + SolcStandardJsonInputSettingsOptimizer::new( + true, + None, + &solc_version.default, + false, + false, + ), + None, + pipeline == SolcPipeline::Yul, + None, + )?; + + let mut output = solc.standard_json(input, pipeline, None, vec![], None)?; + + let project = output.try_to_project(sources, libraries, pipeline, &solc_version, None)?; + + let build: crate::Build = project.compile(optimizer_settings, false, false, false, None)?; + build.write_to_standard_json( + &mut output, + &solc_version, + &semver::Version::from_str(env!("CARGO_PKG_VERSION"))?, + )?; + + Ok(output) +} + +/// +/// Builds the Solidity project and returns the standard JSON output. +/// +pub fn build_solidity_and_detect_missing_libraries( + sources: BTreeMap, + libraries: BTreeMap>, + pipeline: SolcPipeline, +) -> anyhow::Result { + check_dependencies(); + + inkwell::support::enable_llvm_pretty_stack_trace(); + era_compiler_llvm_context::initialize_target(era_compiler_llvm_context::Target::PVM); + let _ = crate::process::EXECUTABLE.set(PathBuf::from(crate::r#const::DEFAULT_EXECUTABLE_NAME)); + + let mut solc = SolcCompiler::new(SolcCompiler::DEFAULT_EXECUTABLE_NAME.to_owned())?; + let solc_version = solc.version()?; + + let input = SolcStandardJsonInput::try_from_sources( + None, + sources.clone(), + libraries.clone(), + None, + SolcStandardJsonInputSettingsSelection::new_required(pipeline), + SolcStandardJsonInputSettingsOptimizer::new( + true, + None, + &solc_version.default, + false, + false, + ), + None, + pipeline == SolcPipeline::Yul, + None, + )?; + + let mut output = solc.standard_json(input, pipeline, None, vec![], None)?; + + let project = output.try_to_project(sources, libraries, pipeline, &solc_version, None)?; + + let missing_libraries = project.get_missing_libraries(); + missing_libraries.write_to_standard_json( + &mut output, + &solc.version()?, + &semver::Version::from_str(env!("CARGO_PKG_VERSION"))?, + )?; + + Ok(output) +} + +/// +/// Checks if the Yul project can be built without errors. +/// +pub fn build_yul(source_code: &str) -> anyhow::Result<()> { + check_dependencies(); + + inkwell::support::enable_llvm_pretty_stack_trace(); + era_compiler_llvm_context::initialize_target(era_compiler_llvm_context::Target::PVM); + let optimizer_settings = era_compiler_llvm_context::OptimizerSettings::none(); + + let project = + Project::try_from_yul_string(PathBuf::from("test.yul").as_path(), source_code, None)?; + let _build = project.compile(optimizer_settings, false, false, false, None)?; + + Ok(()) +} + +/// +/// Checks if the built Solidity project contains the given warning. +/// +pub fn check_solidity_warning( + source_code: &str, + warning_substring: &str, + libraries: BTreeMap>, + pipeline: SolcPipeline, + skip_for_zkvm_edition: bool, + suppressed_warnings: Option>, +) -> anyhow::Result { + check_dependencies(); + + let mut solc = SolcCompiler::new(SolcCompiler::DEFAULT_EXECUTABLE_NAME.to_owned())?; + let solc_version = solc.version()?; + if skip_for_zkvm_edition && solc_version.l2_revision.is_some() { + return Ok(true); + } + + let mut sources = BTreeMap::new(); + sources.insert("test.sol".to_string(), source_code.to_string()); + let input = SolcStandardJsonInput::try_from_sources( + None, + sources.clone(), + libraries, + None, + SolcStandardJsonInputSettingsSelection::new_required(pipeline), + SolcStandardJsonInputSettingsOptimizer::new( + true, + None, + &solc_version.default, + false, + false, + ), + None, + pipeline == SolcPipeline::Yul, + suppressed_warnings, + )?; + + let output = solc.standard_json(input, pipeline, None, vec![], None)?; + let contains_warning = output + .errors + .ok_or_else(|| anyhow::anyhow!("Solidity compiler messages not found"))? + .iter() + .any(|error| error.formatted_message.contains(warning_substring)); + + Ok(contains_warning) +} diff --git a/crates/solidity/src/tests/cli-tests/jest.config.js b/crates/solidity/src/tests/cli-tests/jest.config.js new file mode 100644 index 0000000..a93551f --- /dev/null +++ b/crates/solidity/src/tests/cli-tests/jest.config.js @@ -0,0 +1,4 @@ +module.exports = { + preset: "ts-jest", + testEnvironment: "node", +}; diff --git a/crates/solidity/src/tests/cli-tests/package-lock.json b/crates/solidity/src/tests/cli-tests/package-lock.json new file mode 100644 index 0000000..5e68f73 --- /dev/null +++ b/crates/solidity/src/tests/cli-tests/package-lock.json @@ -0,0 +1,3812 @@ +{ + "name": "cli-tests", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "cli-tests", + "version": "1.0.0", + "license": "ISC", + "devDependencies": { + "@types/jest": "^29.5.11", + "@types/shelljs": "^0.8.15", + "jest": "^29.7.0", + "shelljs": "^0.8.5", + "ts-jest": "^29.1.1", + "typescript": "^5.3.3" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", + "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", + "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.23.4", + "chalk": "^2.4.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/code-frame/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/code-frame/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@babel/code-frame/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/code-frame/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.5.tgz", + "integrity": "sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.23.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.7.tgz", + "integrity": "sha512-+UpDgowcmqe36d4NwqvKsyPMlOLNGMsfMmQ5WGCu+siCe3t3dfe9njrzGfdN4qq+bcNUt0+Vw6haRxBOycs4dw==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.6", + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helpers": "^7.23.7", + "@babel/parser": "^7.23.6", + "@babel/template": "^7.22.15", + "@babel/traverse": "^7.23.7", + "@babel/types": "^7.23.6", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz", + "integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.23.6", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", + "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.23.5", + "@babel/helper-validator-option": "^7.23.5", + "browserslist": "^4.22.2", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", + "dev": true, + "dependencies": { + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", + "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", + "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-simple-access": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/helper-validator-identifier": "^7.22.20" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", + "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", + "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", + "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", + "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.23.8", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.8.tgz", + "integrity": "sha512-KDqYz4PiOWvDFrdHLPhKtCThtIcKVy6avWD2oG4GEvyQ+XDZwHD4YQd+H2vNMnq2rkdxsDkU82T+Vk8U/WXHRQ==", + "dev": true, + "dependencies": { + "@babel/template": "^7.22.15", + "@babel/traverse": "^7.23.7", + "@babel/types": "^7.23.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", + "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/parser": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.6.tgz", + "integrity": "sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.23.3.tgz", + "integrity": "sha512-EB2MELswq55OHUoRZLGg/zC7QWUKfNLpE57m/S2yr1uEneIgsTgrSzXP3NXEsMkVn76OlaVVnzN+ugObuYGwhg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.23.3.tgz", + "integrity": "sha512-9EiNjVJOMwCO+43TqoTrgQ8jMwcAd0sWyXi9RPfIsLTj4R2MADDDQXELhffaUx/uJv2AYcxBgPwH6j4TIA4ytQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.23.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.7.tgz", + "integrity": "sha512-tY3mM8rH9jM0YHFGyfC0/xf+SB5eKUu7HPj7/k3fpi9dAlsMc5YbQvDi0Sh2QTPXqMhyaAtzAr807TIyfQrmyg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.6", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.6", + "@babel/types": "^7.23.6", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.6.tgz", + "integrity": "sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.23.4", + "@babel/helper-validator-identifier": "^7.22.20", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", + "dev": true, + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "dev": true, + "dependencies": { + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "dev": true, + "dependencies": { + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "dev": true, + "dependencies": { + "jest-get-type": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "dev": true, + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "dev": true, + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", + "dev": true, + "dependencies": { + "@jest/test-result": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.20", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", + "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", + "integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", + "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.5.tgz", + "integrity": "sha512-WXCyOcRtH37HAUkpXhUduaxdm82b4GSlyTqajXviN4EfiuPgNYR109xMCKvpl6zPIpua0DGlMEDCq+g8EdoheQ==", + "dev": true, + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==", + "dev": true, + "dependencies": { + "@types/minimatch": "*", + "@types/node": "*" + } + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "29.5.11", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.11.tgz", + "integrity": "sha512-S2mHmYIVe13vrm6q4kN6fLYYAka15ALQki/vgDC3mIukEOx8WJlv0kQPM+d4w8Gp6u0uSdKND04IlTXBv0rwnQ==", + "dev": true, + "dependencies": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, + "node_modules/@types/minimatch": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", + "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==", + "dev": true + }, + "node_modules/@types/node": { + "version": "20.11.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.0.tgz", + "integrity": "sha512-o9bjXmDNcF7GbM4CNQpmi+TutCgap/K3w1JyKgxAjqx41zp9qlIAVFi0IhCNsJcXolEqLWhbFbEeL0PvYm4pcQ==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/shelljs": { + "version": "0.8.15", + "resolved": "https://registry.npmjs.org/@types/shelljs/-/shelljs-0.8.15.tgz", + "integrity": "sha512-vzmnCHl6hViPu9GNLQJ+DZFd6BQI2DBTUeOvYHqkWQLMfKAAQYMb/xAmZkTogZI/vqXHCWkqDRymDI5p0QTi5Q==", + "dev": true, + "dependencies": { + "@types/glob": "~7.2.0", + "@types/node": "*" + } + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true + }, + "node_modules/@types/yargs": { + "version": "17.0.32", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", + "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/babel-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "dev": true, + "dependencies": { + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "dev": true, + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", + "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", + "dev": true, + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.8.3", + "@babel/plugin-syntax-import-meta": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.8.3", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-top-level-await": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/babel-preset-jest": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "dev": true, + "dependencies": { + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.2.tgz", + "integrity": "sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001565", + "electron-to-chromium": "^1.4.601", + "node-releases": "^2.0.14", + "update-browserslist-db": "^1.0.13" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "dependencies": { + "fast-json-stable-stringify": "2.x" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001576", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001576.tgz", + "integrity": "sha512-ff5BdakGe2P3SQsMsiqmt1Lc8221NR1VzHj5jXN5vBny9A6fpze94HiVV/n7XRosOlsShJcvMv5mdnpjOGCEgg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz", + "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==", + "dev": true + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", + "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", + "dev": true + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/create-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", + "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "prompts": "^2.0.1" + }, + "bin": { + "create-jest": "bin/create-jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/dedent": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.1.tgz", + "integrity": "sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg==", + "dev": true, + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.4.628", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.628.tgz", + "integrity": "sha512-2k7t5PHvLsufpP6Zwk0nof62yLOsCf032wZx7/q0mv8gwlXjhcxI3lz6f0jBr0GrnWKcm3burXzI3t5IrcdUxw==", + "dev": true + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "dev": true, + "dependencies": { + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/hasown": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", + "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/import-local": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "dev": true, + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/interpret": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "node_modules/is-core-module": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "dev": true, + "dependencies": { + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.1.tgz", + "integrity": "sha512-EAMEJBsYuyyztxMxW3g7ugGPkrZsV57v0Hmv3mm1uQsmB+QnZuepg731CRaIgeUVSdmsTngOkSnauNF8p7FIhA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.6.tgz", + "integrity": "sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==", + "dev": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", + "dev": true, + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/types": "^29.6.3", + "import-local": "^3.0.2", + "jest-cli": "^29.7.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", + "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", + "dev": true, + "dependencies": { + "execa": "^5.0.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", + "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^1.0.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.7.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-cli": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", + "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", + "dev": true, + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "create-jest": "^29.7.0", + "exit": "^0.1.2", + "import-local": "^3.0.2", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-config": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-docblock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", + "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", + "dev": true, + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", + "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-environment-node": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-leak-detector": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", + "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", + "dev": true, + "dependencies": { + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", + "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", + "dev": true, + "dependencies": { + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", + "dev": true, + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", + "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", + "@jest/source-map": "^29.6.3", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.7.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-snapshot/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-snapshot/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "leven": "^3.1.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watcher": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", + "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", + "dev": true, + "dependencies": { + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.7.0", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "dev": true, + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-dir/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true + }, + "node_modules/node-releases": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", + "dev": true + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-locate/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/pure-rand": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.4.tgz", + "integrity": "sha512-LA0Y9kxMYv47GIPJy6MI84fqTd2HmYZI83W/kM/SkKfDlajnZYfmXFTxkbY+xSBPkLJxltMa9hIkmdc29eguMA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ] + }, + "node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "node_modules/rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==", + "dev": true, + "dependencies": { + "resolve": "^1.1.6" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve.exports": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", + "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/shelljs": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", + "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", + "dev": true, + "dependencies": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + }, + "bin": { + "shjs": "bin/shjs" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-jest": { + "version": "29.1.1", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.1.tgz", + "integrity": "sha512-D6xjnnbP17cC85nliwGiL+tpoKN0StpgE0TeOjXQTU6MVCfsB4v7aW05CgQ/1OywGb0x/oy9hHFnN+sczTiRaA==", + "dev": true, + "dependencies": { + "bs-logger": "0.x", + "fast-json-stable-stringify": "2.x", + "jest-util": "^29.0.0", + "json5": "^2.2.3", + "lodash.memoize": "4.x", + "make-error": "1.x", + "semver": "^7.5.3", + "yargs-parser": "^21.0.1" + }, + "bin": { + "ts-jest": "cli.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0-beta.0 <8", + "@jest/types": "^29.0.0", + "babel-jest": "^29.0.0", + "jest": "^29.0.0", + "typescript": ">=4.3 <6" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@jest/types": { + "optional": true + }, + "babel-jest": { + "optional": true + }, + "esbuild": { + "optional": true + } + } + }, + "node_modules/ts-jest/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ts-jest/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ts-jest/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", + "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, + "node_modules/update-browserslist-db": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", + "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/v8-to-istanbul": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.2.0.tgz", + "integrity": "sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/crates/solidity/src/tests/cli-tests/package.json b/crates/solidity/src/tests/cli-tests/package.json new file mode 100644 index 0000000..0f786d1 --- /dev/null +++ b/crates/solidity/src/tests/cli-tests/package.json @@ -0,0 +1,24 @@ +{ + "name": "cli-tests", + "version": "1.0.0", + "title": "zksolc CLI Tests", + "description": "Auto tests for verifying zksolc CLI", + "repository": "https://github.com/matter-labs/era_compiler-solidity", + "main": "index.js", + "private": true, + "scripts": { + "test": "npx jest --verbose --testPathPattern=" + + }, + "keywords": [], + "author": "Matter Labs", + "license": "MIT", + "devDependencies": { + "@types/jest": "^29.5.11", + "@types/shelljs": "^0.8.15", + "jest": "^29.7.0", + "shelljs": "^0.8.5", + "ts-jest": "^29.1.1", + "typescript": "^5.3.3" + } +} diff --git a/crates/solidity/src/tests/cli-tests/src/contracts/solidity/contract.sol b/crates/solidity/src/tests/cli-tests/src/contracts/solidity/contract.sol new file mode 100644 index 0000000..625af56 --- /dev/null +++ b/crates/solidity/src/tests/cli-tests/src/contracts/solidity/contract.sol @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.0; +contract C {} diff --git a/crates/solidity/src/tests/cli-tests/src/contracts/yul/contract.yul b/crates/solidity/src/tests/cli-tests/src/contracts/yul/contract.yul new file mode 100644 index 0000000..8c582d9 --- /dev/null +++ b/crates/solidity/src/tests/cli-tests/src/contracts/yul/contract.yul @@ -0,0 +1,54 @@ +object "Test" { + code { + { + mstore(64, 128) + if callvalue() { revert(0, 0) } + let _1 := datasize("Test_deployed") + codecopy(0, dataoffset("Test_deployed"), _1) + return(0, _1) + } + } + object "Test_deployed" { + code { + { + mstore(64, 128) + if iszero(lt(calldatasize(), 4)) + { + let _1 := 0 + switch shr(224, calldataload(_1)) + case 0x3df4ddf4 { + if callvalue() { revert(_1, _1) } + if slt(add(calldatasize(), not(3)), _1) { revert(_1, _1) } + let memPos := allocate_memory(_1) + mstore(memPos, 0x2a) + return(memPos, 32) + } + case 0x5a8ac02d { + if callvalue() { revert(_1, _1) } + if slt(add(calldatasize(), not(3)), _1) { revert(_1, _1) } + let memPos_1 := allocate_memory(_1) + return(memPos_1, sub(abi_encode_uint256(memPos_1, 0x63), memPos_1)) + } + } + revert(0, 0) + } + function abi_encode_uint256(headStart, value0) -> tail + { + tail := add(headStart, 32) + mstore(headStart, value0) + } + function allocate_memory(size) -> memPtr + { + memPtr := mload(64) + let newFreePtr := add(memPtr, and(add(size, 31), not(31))) + if or(gt(newFreePtr, 0xffffffffffffffff), lt(newFreePtr, memPtr)) + { + mstore(0, shl(224, 0x4e487b71)) + mstore(4, 0x41) + revert(0, 0x24) + } + mstore(64, newFreePtr) + } + } + } +} diff --git a/crates/solidity/src/tests/cli-tests/src/contracts/zkasm/contract.zkasm b/crates/solidity/src/tests/cli-tests/src/contracts/zkasm/contract.zkasm new file mode 100644 index 0000000..e17dc61 --- /dev/null +++ b/crates/solidity/src/tests/cli-tests/src/contracts/zkasm/contract.zkasm @@ -0,0 +1,25 @@ + .text + .file "main" + .globl __entry +__entry: +.func_begin0: + sub.s! 0, r2, r1 + jump.eq @.BB0_2 + add 32, r0, r1 + st.1 r0, r1 + st.1 r1, r0 + add @CPI0_1[0], r0, r1 + ret.ok.to_label r1, @DEFAULT_FAR_RETURN +.BB0_2: + add 42, r0, r1 + st.1 r0, r1 + add @CPI0_0[0], r0, r1 + ret.ok.to_label r1, @DEFAULT_FAR_RETURN +.func_end0: + + .note.GNU-stack + .rodata +CPI0_0: + .cell 2535301200456458802993406410752 +CPI0_1: + .cell 5070602400912917605986812821504 diff --git a/crates/solidity/src/tests/cli-tests/src/entities.ts b/crates/solidity/src/tests/cli-tests/src/entities.ts new file mode 100644 index 0000000..72931a9 --- /dev/null +++ b/crates/solidity/src/tests/cli-tests/src/entities.ts @@ -0,0 +1,31 @@ +import * as path from 'path'; + +const outputDir = 'artifacts'; +const binExtension = ':C.zbin'; +const asmExtension = ':C.zasm'; +const contractSolFilename = 'contract.sol'; +const contractYulFilename = 'contract.yul'; +const contractZkasmFilename = 'contract.zkasm'; +const pathToOutputDir = path.join( __dirname, '..', outputDir); +const pathToContracts = path.join( __dirname, '..', 'src', 'contracts'); +const pathToBasicYulContract = path.join(pathToContracts, 'yul', contractYulFilename); +const pathToBasicZkasmContract = path.join(pathToContracts, 'zkasm', contractZkasmFilename); +const pathToBasicSolContract = path.join(pathToContracts, 'solidity', contractSolFilename); +const pathToSolBinOutputFile = path.join(pathToOutputDir, contractSolFilename + binExtension); +const pathToSolAsmOutputFile = path.join(pathToOutputDir, contractSolFilename + asmExtension); + +export const paths = { + outputDir: outputDir, + binExtension: binExtension, + asmExtension: asmExtension, + contractSolFilename: contractSolFilename, + contractZkasmFilename: contractZkasmFilename, + contractYulFilename: contractYulFilename, + pathToOutputDir: pathToOutputDir, + pathToContracts: pathToContracts, + pathToBasicZkasmContract: pathToBasicZkasmContract, + pathToBasicSolContract: pathToBasicSolContract, + pathToBasicYulContract: pathToBasicYulContract, + pathToSolBinOutputFile: pathToSolBinOutputFile, + pathToSolAsmOutputFile: pathToSolAsmOutputFile, +}; diff --git a/crates/solidity/src/tests/cli-tests/src/helper.ts b/crates/solidity/src/tests/cli-tests/src/helper.ts new file mode 100644 index 0000000..4a7fc6d --- /dev/null +++ b/crates/solidity/src/tests/cli-tests/src/helper.ts @@ -0,0 +1,29 @@ +import * as shell from 'shelljs'; +import * as fs from 'fs'; + +interface CommandResult { + output: string; + exitCode: number; +} + +export const executeCommand = (command: string): CommandResult => { + const result = shell.exec(command, {async: false}); + return { + exitCode: result.code, + output: result.stdout.trim() || result.stderr.trim(), + }; +}; + +export const isFolderExist = (folder: string): boolean => { + return shell.test('-d', folder); +}; + +export const isFileExist = (pathToFileDir: string, fileName: string, fileExtension:string): boolean => { + return shell.ls(pathToFileDir).stdout.includes(fileName + fileExtension); +}; + +export const isFileEmpty = (file: string): boolean => { + if (fs.existsSync(file)) { + return (fs.readFileSync(file).length === 0); + } +}; diff --git a/crates/solidity/src/tests/cli-tests/tests/asm.test.ts b/crates/solidity/src/tests/cli-tests/tests/asm.test.ts new file mode 100644 index 0000000..7c88a0c --- /dev/null +++ b/crates/solidity/src/tests/cli-tests/tests/asm.test.ts @@ -0,0 +1,39 @@ +import {executeCommand} from "../src/helper"; +import { paths } from '../src/entities'; + + +//id1746 +describe("Run with --asm by default", () => { + const command = `zksolc ${paths.pathToBasicSolContract} --asm`; + const result = executeCommand(command); + const commandInvalid = 'zksolc --asm'; + const resultInvalid = executeCommand(commandInvalid); + + it("Valid command exit code = 0", () => { + expect(result.exitCode).toBe(0); + }); + + it("--asm output is presented", () => { + expect(result.output).toMatch(/(__entry:)/i); + }); + + + it("solc exit code == zksolc exit code", () => { + const command = `solc ${paths.pathToBasicSolContract} --asm`; + const solcResult = executeCommand(command); + expect(solcResult.exitCode).toBe(result.exitCode); + }); + + it("run invalid: zksolc --asm", () => { + expect(resultInvalid.output).toMatch(/(No input sources specified|Compilation aborted)/i); + }); + it("Invalid command exit code = 1", () => { + expect(resultInvalid.exitCode).toBe(1); + }); + + it("Invalid solc exit code == Invalid zksolc exit code", () => { + const command = 'solc --asm'; + const solcResult = executeCommand(command); + expect(solcResult.exitCode).toBe(resultInvalid.exitCode); + }); +}); diff --git a/crates/solidity/src/tests/cli-tests/tests/common.test.ts b/crates/solidity/src/tests/cli-tests/tests/common.test.ts new file mode 100644 index 0000000..74ab073 --- /dev/null +++ b/crates/solidity/src/tests/cli-tests/tests/common.test.ts @@ -0,0 +1,78 @@ +import {executeCommand, isFolderExist, isFileExist, isFileEmpty} from "../src/helper"; +import { paths } from '../src/entities'; + + +//id1762 +describe("Run zksolc without any options", () => { + const command = 'zksolc'; + const result = executeCommand(command); + + it("Info with help is presented", () => { + expect(result.output).toMatch(/(No input sources specified|Error(s) found.)/i); + }); + + it("Exit code = 1", () => { + expect(result.exitCode).toBe(1); + }); + + it("solc exit code == zksolc exit code", () => { + const command = 'solc'; + const solcResult = executeCommand(command); + expect(solcResult.exitCode).toBe(result.exitCode); + }); +}); + + +//#1713 +describe("Default run a command from the help", () => { + + const command = `zksolc ${paths.pathToBasicSolContract} -O3 --bin --output-dir "${paths.pathToOutputDir}"`; // potential issue on zksolc with full path on Windows cmd + const result = executeCommand(command); + + it("Compiler run successful", () => { + expect(result.output).toMatch(/(Compiler run successful.)/i); + }); + it("Exit code = 0", () => { + expect(result.exitCode).toBe(0); + }); + it("Output dir is created", () => { + expect(isFolderExist(paths.pathToOutputDir)).toBe(true); + }); + xit("Output file is created", () => { // a bug on windows + expect(isFileExist(paths.pathToOutputDir, paths.contractSolFilename, paths.binExtension)).toBe(true); + }); + it("the output file is not empty", () => { + expect(isFileEmpty(paths.pathToSolBinOutputFile)).toBe(false); + }); + it("No 'Error'/'Warning'/'Fail' in the output", () => { + expect(result.output).not.toMatch(/([Ee]rror|[Ww]arning|[Ff]ail)/i); + }); +}); + +//#1818 +describe("Default run a command from the help", () => { + + const command = `zksolc ${paths.pathToBasicSolContract} -O3 --bin --asm --output-dir "${paths.pathToOutputDir}"`; // potential issue on zksolc with full path on Windows cmd + const result = executeCommand(command); + + it("Compiler run successful", () => { + expect(result.output).toMatch(/(Compiler run successful.)/i); + }); + it("Exit code = 0", () => { + expect(result.exitCode).toBe(0); + }); + it("Output dir is created", () => { + expect(isFolderExist(paths.pathToOutputDir)).toBe(true); + }); + xit("Output files are created", () => { // a bug on windows + expect(isFileExist(paths.pathToOutputDir, paths.contractSolFilename, paths.binExtension)).toBe(true); + expect(isFileExist(paths.pathToOutputDir, paths.contractSolFilename, paths.asmExtension)).toBe(true); + }); + it("the output files are not empty", () => { + expect(isFileEmpty(paths.pathToSolBinOutputFile)).toBe(false); + expect(isFileEmpty(paths.pathToSolAsmOutputFile)).toBe(false); + }); + it("No 'Error'/'Warning'/'Fail' in the output", () => { + expect(result.output).not.toMatch(/([Ee]rror|[Ww]arning|[Ff]ail)/i); + }); +}); diff --git a/crates/solidity/src/tests/cli-tests/tests/yul.test.ts b/crates/solidity/src/tests/cli-tests/tests/yul.test.ts new file mode 100644 index 0000000..ee01821 --- /dev/null +++ b/crates/solidity/src/tests/cli-tests/tests/yul.test.ts @@ -0,0 +1,40 @@ +import {executeCommand} from "../src/helper"; +import { paths } from '../src/entities'; + + +//id1743 +describe("Run with --yul by default", () => { + const command = `zksolc ${paths.pathToBasicYulContract} --yul`; + const result = executeCommand(command); + const commandInvalid = 'zksolc --yul'; + const resultInvalid = executeCommand(commandInvalid); + + it("Valid command exit code = 0", () => { + expect(result.exitCode).toBe(0); + }); + + it("--yul output is presented", () => { + expect(result.output).toMatch(/(Compiler run successful)/i); + expect(result.output).toMatch(/(No output requested)/i); + }); + + + xit("solc exit code == zksolc exit code", () => { // unknown solc issue for datatype of the contract + const command = `solc ${paths.pathToBasicSolContract} --yul`; + const solcResult = executeCommand(command); + expect(solcResult.exitCode).toBe(result.exitCode); + }); + + it("run invalid: zksolc --yul", () => { + expect(resultInvalid.output).toMatch(/(The input file is missing)/i); + }); + it("Invalid command exit code = 1", () => { + expect(resultInvalid.exitCode).toBe(1); + }); + + it("Invalid solc exit code == Invalid zksolc exit code", () => { + const command = 'solc --yul'; + const solcResult = executeCommand(command); + expect(solcResult.exitCode).toBe(resultInvalid.exitCode); + }); +}); diff --git a/crates/solidity/src/tests/cli-tests/tests/zkasm.test.ts b/crates/solidity/src/tests/cli-tests/tests/zkasm.test.ts new file mode 100644 index 0000000..b6fc9b9 --- /dev/null +++ b/crates/solidity/src/tests/cli-tests/tests/zkasm.test.ts @@ -0,0 +1,18 @@ +import {executeCommand} from "../src/helper"; +import { paths } from '../src/entities'; + + +//id1745 +describe("Run with --zkasm by default", () => { + const command = `zksolc ${paths.pathToBasicZkasmContract} --zkasm`; + const result = executeCommand(command); + + it("Valid command exit code = 0", () => { + expect(result.exitCode).toBe(0); + }); + + it("--zkasm output is presented", () => { + expect(result.output).toMatch(/(Compiler run successful)/i); + expect(result.output).toMatch(/(No output requested)/i); + }); +}); diff --git a/crates/solidity/src/tests/cli-tests/tsconfig.json b/crates/solidity/src/tests/cli-tests/tsconfig.json new file mode 100644 index 0000000..9a23735 --- /dev/null +++ b/crates/solidity/src/tests/cli-tests/tsconfig.json @@ -0,0 +1,8 @@ +{ + "compilerOptions": { + "target": "ES6", + "module": "CommonJS", + "outDir": "./dist", + "rootDir": "./src" + } +} diff --git a/crates/solidity/src/tests/factory_dependency.rs b/crates/solidity/src/tests/factory_dependency.rs new file mode 100644 index 0000000..26431db --- /dev/null +++ b/crates/solidity/src/tests/factory_dependency.rs @@ -0,0 +1,93 @@ +//! +//! The Solidity compiler unit tests for factory dependencies. +//! + +#![cfg(test)] + +use std::collections::BTreeMap; + +use crate::solc::pipeline::Pipeline as SolcPipeline; + +pub const MAIN_CODE: &str = r#" +// SPDX-License-Identifier: MIT + +pragma solidity >=0.4.16; + +import "./callable.sol"; + +contract Main { + function main() external returns(uint256) { + Callable callable = new Callable(); + + callable.set(10); + return callable.get(); + } +} +"#; + +pub const CALLABLE_CODE: &str = r#" +// SPDX-License-Identifier: MIT + +pragma solidity >=0.4.16; + +contract Callable { + uint256 value; + + function set(uint256 x) external { + value = x; + } + + function get() external view returns(uint256) { + return value; + } +} +"#; + +#[test] +fn default() { + let mut sources = BTreeMap::new(); + sources.insert("main.sol".to_owned(), MAIN_CODE.to_owned()); + sources.insert("callable.sol".to_owned(), CALLABLE_CODE.to_owned()); + + let output = super::build_solidity( + sources, + BTreeMap::new(), + None, + SolcPipeline::Yul, + era_compiler_llvm_context::OptimizerSettings::cycles(), + ) + .expect("Build failure"); + + assert_eq!( + output + .contracts + .as_ref() + .expect("Missing field `contracts`") + .get("main.sol") + .expect("Missing file `main.sol`") + .get("Main") + .expect("Missing contract `main.sol:Main`") + .factory_dependencies + .as_ref() + .expect("Missing field `factory_dependencies`") + .len(), + 1, + "Expected 1 factory dependency in `main.sol:Main`" + ); + assert_eq!( + output + .contracts + .as_ref() + .expect("Missing field `contracts`") + .get("callable.sol") + .expect("Missing file `callable.sol`") + .get("Callable") + .expect("Missing contract `callable.sol:Callable`") + .factory_dependencies + .as_ref() + .expect("Missing field `factory_dependencies`") + .len(), + 0, + "Expected 0 factory dependencies in `callable.sol:Callable`" + ); +} diff --git a/crates/solidity/src/tests/ir_artifacts.rs b/crates/solidity/src/tests/ir_artifacts.rs new file mode 100644 index 0000000..a835a73 --- /dev/null +++ b/crates/solidity/src/tests/ir_artifacts.rs @@ -0,0 +1,122 @@ +//! +//! The Solidity compiler unit tests for IR artifacts. +//! +//! The tests check if the IR artifacts are kept in the final output. +//! + +#![cfg(test)] + +use std::collections::BTreeMap; + +use crate::solc::pipeline::Pipeline as SolcPipeline; + +#[test] +fn yul() { + let source_code = r#" +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +contract Test { + function main() public view returns (uint) { + return 42; + } +} + "#; + + let mut sources = BTreeMap::new(); + sources.insert("test.sol".to_owned(), source_code.to_owned()); + + let build = super::build_solidity( + sources, + BTreeMap::new(), + None, + SolcPipeline::Yul, + era_compiler_llvm_context::OptimizerSettings::cycles(), + ) + .expect("Test failure"); + + assert!( + build + .contracts + .as_ref() + .expect("Always exists") + .get("test.sol") + .expect("Always exists") + .get("Test") + .expect("Always exists") + .ir_optimized + .is_some(), + "Yul IR is missing" + ); + assert!( + build + .contracts + .as_ref() + .expect("Always exists") + .get("test.sol") + .expect("Always exists") + .get("Test") + .expect("Always exists") + .evm + .as_ref() + .expect("EVM object is missing") + .assembly + .is_none(), + "EVMLA IR is present although not requested" + ); +} + +#[test] +fn evmla() { + let source_code = r#" +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +contract Test { + function main() public view returns (uint) { + return 42; + } +} + "#; + + let mut sources = BTreeMap::new(); + sources.insert("test.sol".to_owned(), source_code.to_owned()); + + let build = super::build_solidity( + sources, + BTreeMap::new(), + None, + SolcPipeline::EVMLA, + era_compiler_llvm_context::OptimizerSettings::cycles(), + ) + .expect("Test failure"); + assert!( + build + .contracts + .as_ref() + .expect("Always exists") + .get("test.sol") + .expect("Always exists") + .get("Test") + .expect("Always exists") + .evm + .as_ref() + .expect("EVM object is missing") + .assembly + .is_some(), + "EVMLA IR is missing", + ); + assert!( + build + .contracts + .as_ref() + .expect("Always exists") + .get("test.sol") + .expect("Always exists") + .get("Test") + .expect("Always exists") + .ir_optimized + .is_none(), + "Yul IR is present although not requested", + ); +} diff --git a/crates/solidity/src/tests/libraries.rs b/crates/solidity/src/tests/libraries.rs new file mode 100644 index 0000000..65e42b5 --- /dev/null +++ b/crates/solidity/src/tests/libraries.rs @@ -0,0 +1,104 @@ +//! +//! The Solidity compiler unit tests for libraries. +//! + +#![cfg(test)] + +use std::collections::BTreeMap; + +use crate::solc::pipeline::Pipeline as SolcPipeline; + +pub const LIBRARY_TEST_SOURCE: &str = r#" +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +// A simple library with at least one external method +library SimpleLibrary { + function add(uint256 a, uint256 b) external pure returns (uint256) { + return a + b; + } +} + +// A contract calling that library +contract SimpleContract { + using SimpleLibrary for uint256; + + function performAlgorithm(uint256 a, uint256 b) public pure returns (uint256) { + uint sum = 0; + if (a > b) { + while (true) { + sum += a.add(b); + } + } + return sum; + } +} + "#; + +#[test] +fn not_specified() { + let mut sources = BTreeMap::new(); + sources.insert("test.sol".to_owned(), LIBRARY_TEST_SOURCE.to_owned()); + + for pipeline in [SolcPipeline::EVMLA, SolcPipeline::Yul] { + let output = super::build_solidity_and_detect_missing_libraries( + sources.clone(), + BTreeMap::new(), + pipeline, + ) + .expect("Test failure"); + assert!( + output + .contracts + .as_ref() + .expect("Always exists") + .get("test.sol") + .expect("Always exists") + .get("SimpleContract") + .expect("Always exists") + .missing_libraries + .as_ref() + .expect("Always exists") + .contains("test.sol:SimpleLibrary"), + "Missing library not detected" + ); + } +} + +#[test] +fn specified() { + let mut sources = BTreeMap::new(); + sources.insert("test.sol".to_owned(), LIBRARY_TEST_SOURCE.to_owned()); + + let mut libraries = BTreeMap::new(); + libraries + .entry("test.sol".to_string()) + .or_insert_with(BTreeMap::new) + .entry("SimpleLibrary".to_string()) + .or_insert("0x00000000000000000000000000000000DEADBEEF".to_string()); + + for pipeline in [SolcPipeline::EVMLA, SolcPipeline::Yul] { + let output = super::build_solidity_and_detect_missing_libraries( + sources.clone(), + libraries.clone(), + pipeline, + ) + .expect("Test failure"); + assert!( + output + .contracts + .as_ref() + .expect("Always exists") + .get("test.sol") + .expect("Always exists") + .get("SimpleContract") + .expect("Always exists") + .missing_libraries + .as_ref() + .cloned() + .unwrap_or_default() + .is_empty(), + "The list of missing libraries must be empty" + ); + } +} diff --git a/crates/solidity/src/tests/messages.rs b/crates/solidity/src/tests/messages.rs new file mode 100644 index 0000000..a0bed61 --- /dev/null +++ b/crates/solidity/src/tests/messages.rs @@ -0,0 +1,394 @@ +//! +//! The Solidity compiler unit tests for messages. +//! + +#![cfg(test)] + +use std::collections::BTreeMap; + +use crate::solc::pipeline::Pipeline as SolcPipeline; +use crate::warning::Warning; + +pub const ECRECOVER_TEST_SOURCE: &str = r#" +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +contract ECRecoverExample { + function recoverAddress( + bytes32 messageHash, + uint8 v, + bytes32 r, + bytes32 s + ) public pure returns (address) { + return ecrecover(messageHash, v, r, s); + } +} + "#; + +#[test] +fn ecrecover() { + assert!( + super::check_solidity_warning( + ECRECOVER_TEST_SOURCE, + "Warning: It looks like you are using 'ecrecover' to validate a signature of a user account.", + BTreeMap::new(), + SolcPipeline::Yul, + false, + None, + ).expect("Test failure") + ); +} + +#[test] +fn ecrecover_suppressed() { + assert!( + !super::check_solidity_warning( + ECRECOVER_TEST_SOURCE, + "Warning: It looks like you are using 'ecrecover' to validate a signature of a user account.", + BTreeMap::new(), + SolcPipeline::Yul, + false, + Some(vec![Warning::EcRecover]), + ).expect("Test failure") + ); +} + +pub const SEND_TEST_SOURCE: &str = r#" +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +contract SendExample { + address payable public recipient; + + constructor(address payable _recipient) { + recipient = _recipient; + } + + function forwardEther() external payable { + bool success = recipient.send(msg.value); + require(success, "Failed to send Ether"); + } +} + "#; + +#[test] +fn send() { + assert!( + super::check_solidity_warning( + SEND_TEST_SOURCE, + "Warning: It looks like you are using '
.send/transfer()' without providing", + BTreeMap::new(), + SolcPipeline::Yul, + false, + None, + ).expect("Test failure") + ); +} + +#[test] +fn send_suppressed() { + assert!( + !super::check_solidity_warning( + SEND_TEST_SOURCE, + "Warning: It looks like you are using '
.send/transfer()' without providing", + BTreeMap::new(), + SolcPipeline::Yul, + false, + Some(vec![Warning::SendTransfer]), + ).expect("Test failure") + ); +} + +pub const TRANSFER_TEST_SOURCE: &str = r#" +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +contract TransferExample { + address payable public recipient; + + constructor(address payable _recipient) { + recipient = _recipient; + } + + function forwardEther() external payable { + recipient.transfer(msg.value); + } +} + "#; + +#[test] +fn transfer() { + assert!( + super::check_solidity_warning( + TRANSFER_TEST_SOURCE, + "Warning: It looks like you are using '
.send/transfer()' without providing", + BTreeMap::new(), + SolcPipeline::Yul, + false, + None, + ).expect("Test failure") + ); +} + +#[test] +fn transfer_suppressed() { + assert!( + !super::check_solidity_warning( + TRANSFER_TEST_SOURCE, + "Warning: It looks like you are using '
.send/transfer()' without providing", + BTreeMap::new(), + SolcPipeline::Yul, + false, + Some(vec![Warning::SendTransfer]), + ).expect("Test failure") + ); +} + +pub const EXTCODESIZE_TEST_SOURCE: &str = r#" +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +contract ExternalCodeSize { + function getExternalCodeSize(address target) public view returns (uint256) { + uint256 codeSize; + assembly { + codeSize := extcodesize(target) + } + return codeSize; + } +} + "#; + +#[test] +fn extcodesize() { + assert!(super::check_solidity_warning( + EXTCODESIZE_TEST_SOURCE, + "Warning: Your code or one of its dependencies uses the 'extcodesize' instruction,", + BTreeMap::new(), + SolcPipeline::Yul, + false, + None, + ) + .expect("Test failure")); +} + +#[test] +fn extcodesize_suppressed() { + assert!(!super::check_solidity_warning( + EXTCODESIZE_TEST_SOURCE, + "Warning: Your code or one of its dependencies uses the 'extcodesize' instruction,", + BTreeMap::new(), + SolcPipeline::Yul, + false, + Some(vec![Warning::ExtCodeSize]), + ) + .expect("Test failure")); +} + +pub const TX_ORIGIN_TEST_SOURCE: &str = r#" +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +contract TxOriginExample { + function isOriginSender() public view returns (bool) { + return tx.origin == msg.sender; + } +} + "#; + +#[test] +fn tx_origin() { + assert!(super::check_solidity_warning( + TX_ORIGIN_TEST_SOURCE, + "Warning: You are checking for 'tx.origin' in your code, which might lead to", + BTreeMap::new(), + SolcPipeline::Yul, + false, + None, + ) + .expect("Test failure")); +} + +#[test] +fn tx_origin_suppressed() { + assert!(!super::check_solidity_warning( + TX_ORIGIN_TEST_SOURCE, + "Warning: You are checking for 'tx.origin' in your code, which might lead to", + BTreeMap::new(), + SolcPipeline::Yul, + false, + Some(vec![Warning::TxOrigin]), + ) + .expect("Test failure")); +} + +pub const TX_ORIGIN_ASSEMBLY_TEST_SOURCE: &str = r#" +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +contract TxOriginExample { + function isOriginSender() public view returns (bool) { + address txOrigin; + address sender = msg.sender; + + assembly { + txOrigin := origin() // Get the transaction origin using the 'origin' instruction + } + + return txOrigin == sender; + } +} + "#; + +#[test] +fn tx_origin_assembly() { + assert!(super::check_solidity_warning( + TX_ORIGIN_ASSEMBLY_TEST_SOURCE, + "Warning: You are checking for 'tx.origin' in your code, which might lead to", + BTreeMap::new(), + SolcPipeline::Yul, + false, + None, + ) + .expect("Test failure")); +} + +#[test] +fn tx_origin_assembly_suppressed() { + assert!(!super::check_solidity_warning( + TX_ORIGIN_ASSEMBLY_TEST_SOURCE, + "Warning: You are checking for 'tx.origin' in your code, which might lead to", + BTreeMap::new(), + SolcPipeline::Yul, + false, + Some(vec![Warning::TxOrigin]), + ) + .expect("Test failure")); +} + +#[test] +fn internal_function_pointer_argument() { + let source_code = r#" +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +contract InternalFunctionPointerExample { + function add(uint256 a, uint256 b) internal pure returns (uint256) { + return a + b; + } + + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + return a - b; + } + + function executeOperation( + function (uint256, uint256) internal pure returns (uint256) operation, + uint256 a, + uint256 b + ) private pure returns (uint256) { + return operation(a, b); + } + + function testAdd(uint256 a, uint256 b) public pure returns (uint256) { + return executeOperation(add, a, b); + } + + function testSub(uint256 a, uint256 b) public pure returns (uint256) { + return executeOperation(sub, a, b); + } +} + "#; + + assert!(super::check_solidity_warning( + source_code, + "Error: Internal function pointers are not supported in EVM legacy assembly pipeline.", + BTreeMap::new(), + SolcPipeline::EVMLA, + true, + None, + ) + .expect("Test failure")); +} + +#[test] +fn internal_function_pointer_stack() { + let source_code = r#" +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +contract StackFunctionPointerExample { + function add(uint256 a, uint256 b) internal pure returns (uint256) { + return a + b; + } + + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + return a - b; + } + + function testAdd(uint256 a, uint256 b) public pure returns (uint256) { + function (uint256, uint256) internal pure returns (uint256) operation = add; + return operation(a, b); + } + + function testSub(uint256 a, uint256 b) public pure returns (uint256) { + function (uint256, uint256) internal pure returns (uint256) operation = sub; + return operation(a, b); + } +} + "#; + + assert!(super::check_solidity_warning( + source_code, + "Error: Internal function pointers are not supported in EVM legacy assembly pipeline.", + BTreeMap::new(), + SolcPipeline::EVMLA, + true, + None, + ) + .expect("Test failure")); +} + +#[test] +fn internal_function_pointer_storage() { + let source_code = r#" +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +contract StorageFunctionPointerExample { + function add(uint256 a, uint256 b) internal pure returns (uint256) { + return a + b; + } + + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + return a - b; + } + + function (uint256, uint256) internal pure returns (uint256) operation; + bool private isOperationSet = false; + + function setOperation(bool isAdd) public { + if (isAdd) { + operation = add; + } else { + operation = sub; + } + isOperationSet = true; + } + + function executeOperation(uint256 a, uint256 b) public view returns (uint256) { + require(isOperationSet, "Operation not set"); + return operation(a, b); + } +} + "#; + + assert!(super::check_solidity_warning( + source_code, + "Error: Internal function pointers are not supported in EVM legacy assembly pipeline.", + BTreeMap::new(), + SolcPipeline::EVMLA, + true, + None, + ) + .expect("Test failure")); +} diff --git a/crates/solidity/src/tests/mod.rs b/crates/solidity/src/tests/mod.rs new file mode 100644 index 0000000..5a9cd88 --- /dev/null +++ b/crates/solidity/src/tests/mod.rs @@ -0,0 +1,16 @@ +//! +//! The Solidity compiler unit tests. +//! + +#![cfg(test)] + +mod factory_dependency; +mod ir_artifacts; +mod libraries; +mod messages; +mod optimizer; +mod remappings; +mod runtime_code; +mod unsupported_opcodes; + +pub(crate) use super::test_utils::*; diff --git a/crates/solidity/src/tests/optimizer.rs b/crates/solidity/src/tests/optimizer.rs new file mode 100644 index 0000000..082a288 --- /dev/null +++ b/crates/solidity/src/tests/optimizer.rs @@ -0,0 +1,137 @@ +//! +//! The Solidity compiler unit tests for the optimizer. +//! + +#![cfg(test)] + +use std::collections::BTreeMap; + +use crate::solc::pipeline::Pipeline as SolcPipeline; + +pub const SOURCE_CODE: &str = r#" +// SPDX-License-Identifier: MIT + +pragma solidity >=0.5.0; + +contract Test { + uint8 constant ARRAY_SIZE = 40; + uint128 constant P = 257; + uint128 constant MODULO = 1000000007; + + function complex() public pure returns(uint64) { + uint8[ARRAY_SIZE] memory array; + // generate array where first half equals second + for(uint8 i = 0; i < ARRAY_SIZE; i++) { + array[i] = (i % (ARRAY_SIZE / 2)) * (255 / (ARRAY_SIZE / 2 - 1)); + } + + bool result = true; + for(uint8 i = 0; i < ARRAY_SIZE/2; i++) { + result = result && hash(array, 0, i + 1) == hash(array, ARRAY_SIZE/2, ARRAY_SIZE/2 + i + 1) + && hash(array, i, ARRAY_SIZE/2) == hash(array, i + ARRAY_SIZE/2, ARRAY_SIZE); + } + if (result) { + return 1; + } else { + return 0; + } + } + + function hash(uint8[ARRAY_SIZE] memory array, uint8 begin, uint8 end) private pure returns(uint128) { + uint128 h = 0; + for(uint8 i = begin; i < end; i++) { + h = (h * P + array[i]) % MODULO; + } + return h; + } +} +"#; + +#[test] +fn optimizer() { + let mut sources = BTreeMap::new(); + sources.insert("test.sol".to_owned(), SOURCE_CODE.to_owned()); + + let build_unoptimized = super::build_solidity( + sources.clone(), + BTreeMap::new(), + None, + SolcPipeline::Yul, + era_compiler_llvm_context::OptimizerSettings::none(), + ) + .expect("Build failure"); + let build_optimized_for_cycles = super::build_solidity( + sources.clone(), + BTreeMap::new(), + None, + SolcPipeline::Yul, + era_compiler_llvm_context::OptimizerSettings::cycles(), + ) + .expect("Build failure"); + let build_optimized_for_size = super::build_solidity( + sources, + BTreeMap::new(), + None, + SolcPipeline::Yul, + era_compiler_llvm_context::OptimizerSettings::size(), + ) + .expect("Build failure"); + + let size_when_unoptimized = build_unoptimized + .contracts + .as_ref() + .expect("Missing field `contracts`") + .get("test.sol") + .expect("Missing file `test.sol`") + .get("Test") + .expect("Missing contract `test.sol:Test`") + .evm + .as_ref() + .expect("Missing EVM data") + .bytecode + .as_ref() + .expect("Missing bytecode") + .object + .len(); + let size_when_optimized_for_cycles = build_optimized_for_cycles + .contracts + .as_ref() + .expect("Missing field `contracts`") + .get("test.sol") + .expect("Missing file `test.sol`") + .get("Test") + .expect("Missing contract `test.sol:Test`") + .evm + .as_ref() + .expect("Missing EVM data") + .bytecode + .as_ref() + .expect("Missing bytecode") + .object + .len(); + let size_when_optimized_for_size = build_optimized_for_size + .contracts + .as_ref() + .expect("Missing field `contracts`") + .get("test.sol") + .expect("Missing file `test.sol`") + .get("Test") + .expect("Missing contract `test.sol:Test`") + .evm + .as_ref() + .expect("Missing EVM data") + .bytecode + .as_ref() + .expect("Missing bytecode") + .object + .len(); + + assert!( + size_when_optimized_for_cycles < size_when_unoptimized, + "Expected the cycles-optimized bytecode to be smaller than the unoptimized. Optimized: {}B, Unoptimized: {}B", size_when_optimized_for_cycles, size_when_unoptimized, + ); + assert!( + size_when_optimized_for_size < size_when_unoptimized, + "Expected the size-optimized bytecode to be smaller than the unoptimized. Optimized: {}B, Unoptimized: {}B", size_when_optimized_for_size, size_when_unoptimized, + ); +} diff --git a/crates/solidity/src/tests/remappings.rs b/crates/solidity/src/tests/remappings.rs new file mode 100644 index 0000000..34b75b6 --- /dev/null +++ b/crates/solidity/src/tests/remappings.rs @@ -0,0 +1,55 @@ +//! +//! The Solidity compiler unit tests for remappings. +//! + +#![cfg(test)] + +use std::collections::BTreeMap; +use std::collections::BTreeSet; + +use crate::solc::pipeline::Pipeline as SolcPipeline; + +pub const CALLEE_TEST_SOURCE: &str = r#" +// SPDX-License-Identifier: MIT + +pragma solidity >=0.4.16; + +contract Callable { + function f(uint a) public pure returns(uint) { + return a * 2; + } +} +"#; + +pub const CALLER_TEST_SOURCE: &str = r#" +// SPDX-License-Identifier: MIT + +pragma solidity >=0.4.16; + +import "libraries/default/callable.sol"; + +contract Main { + function main(Callable callable) public returns(uint) { + return callable.f(5); + } +} +"#; + +#[test] +fn default() { + let mut sources = BTreeMap::new(); + sources.insert("./test.sol".to_owned(), CALLER_TEST_SOURCE.to_owned()); + sources.insert("./callable.sol".to_owned(), CALLEE_TEST_SOURCE.to_owned()); + + let mut remappings = BTreeSet::new(); + remappings.insert("libraries/default/=./".to_owned()); + + super::build_solidity( + sources, + BTreeMap::new(), + Some(remappings), + SolcPipeline::Yul, + era_compiler_llvm_context::OptimizerSettings::cycles(), + ) + .expect("Test failure"); +} diff --git a/crates/solidity/src/tests/runtime_code.rs b/crates/solidity/src/tests/runtime_code.rs new file mode 100644 index 0000000..f7d91d9 --- /dev/null +++ b/crates/solidity/src/tests/runtime_code.rs @@ -0,0 +1,38 @@ +//! +//! The Solidity compiler unit tests for runtime code. +//! + +#![cfg(test)] + +use std::collections::BTreeMap; + +use crate::solc::pipeline::Pipeline as SolcPipeline; + +#[test] +#[should_panic(expected = "runtimeCode is not supported")] +fn default() { + let source_code = r#" +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +contract A {} + +contract Test { + function main() public pure returns(bytes memory) { + return type(A).runtimeCode; + } +} + "#; + + let mut sources = BTreeMap::new(); + sources.insert("test.sol".to_owned(), source_code.to_owned()); + + super::build_solidity( + sources, + BTreeMap::new(), + None, + SolcPipeline::Yul, + era_compiler_llvm_context::OptimizerSettings::cycles(), + ) + .expect("Test failure"); +} diff --git a/crates/solidity/src/tests/unsupported_opcodes.rs b/crates/solidity/src/tests/unsupported_opcodes.rs new file mode 100644 index 0000000..4795d7f --- /dev/null +++ b/crates/solidity/src/tests/unsupported_opcodes.rs @@ -0,0 +1,222 @@ +//! +//! The Solidity compiler unit tests for unsupported opcodes. +//! + +#![cfg(test)] + +use std::collections::BTreeMap; + +use crate::solc::pipeline::Pipeline as SolcPipeline; + +#[test] +#[should_panic(expected = "The `CODECOPY` instruction is not supported")] +fn codecopy_yul_runtime() { + let source_code = r#" +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +contract FixedCodeCopy { + function copyCode() public view returns (bytes memory) { + uint256 fixedCodeSize = 64; + bytes memory code = new bytes(fixedCodeSize); + + assembly { + codecopy(add(code, 0x20), 0, fixedCodeSize) + } + + return code; + } +} + "#; + + let mut sources = BTreeMap::new(); + sources.insert("test.sol".to_owned(), source_code.to_owned()); + + super::build_solidity( + sources, + BTreeMap::new(), + None, + SolcPipeline::Yul, + era_compiler_llvm_context::OptimizerSettings::cycles(), + ) + .expect("Test failure"); +} + +pub const CALLCODE_TEST_SOURCE: &str = r#" +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +contract CallcodeTest { + function testCallcode(address target, bytes4 signature, uint256 inputValue) public returns (bool) { + bool success; + + assembly { + let input := mload(0x40) + mstore(input, signature) + mstore(add(input, 0x04), inputValue) + + let callResult := callcode(gas(), target, 0, input, 0x24, 0, 0) + + success := and(callResult, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) + } + + return success; + } +} + "#; + +#[test] +#[should_panic(expected = "The `CALLCODE` instruction is not supported")] +fn callcode_evmla() { + let mut sources = BTreeMap::new(); + sources.insert("test.sol".to_owned(), CALLCODE_TEST_SOURCE.to_owned()); + + super::build_solidity( + sources, + BTreeMap::new(), + None, + SolcPipeline::EVMLA, + era_compiler_llvm_context::OptimizerSettings::cycles(), + ) + .expect("Test failure"); +} + +#[test] +#[should_panic(expected = "The `CALLCODE` instruction is not supported")] +fn callcode_yul() { + let mut sources = BTreeMap::new(); + sources.insert("test.sol".to_owned(), CALLCODE_TEST_SOURCE.to_owned()); + + super::build_solidity( + sources, + BTreeMap::new(), + None, + SolcPipeline::Yul, + era_compiler_llvm_context::OptimizerSettings::cycles(), + ) + .expect("Test failure"); +} + +#[test] +#[should_panic(expected = "The `PC` instruction is not supported")] +fn pc_yul() { + let source_code = r#" +object "ProgramCounter" { + code { + datacopy(0, dataoffset("ProgramCounter_deployed"), datasize("ProgramCounter_deployed")) + return(0, datasize("ProgramCounter_deployed")) + } + object "ProgramCounter_deployed" { + code { + function getPC() -> programCounter { + programCounter := pc() + } + + let pcValue := getPC() + sstore(0, pcValue) + } + } +} + "#; + + super::build_yul(source_code).expect("Test failure"); +} + +pub const EXTCODECOPY_TEST_SOURCE: &str = r#" +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +contract ExternalCodeCopy { + function copyExternalCode(address target, uint256 codeSize) public view returns (bytes memory) { + bytes memory code = new bytes(codeSize); + + assembly { + extcodecopy(target, add(code, 0x20), 0, codeSize) + } + + return code; + } +} + "#; + +#[test] +#[should_panic(expected = "The `EXTCODECOPY` instruction is not supported")] +fn extcodecopy_evmla() { + let mut sources = BTreeMap::new(); + sources.insert("test.sol".to_owned(), EXTCODECOPY_TEST_SOURCE.to_owned()); + + super::build_solidity( + sources, + BTreeMap::new(), + None, + SolcPipeline::EVMLA, + era_compiler_llvm_context::OptimizerSettings::cycles(), + ) + .expect("Test failure"); +} + +#[test] +#[should_panic(expected = "The `EXTCODECOPY` instruction is not supported")] +fn extcodecopy_yul() { + let mut sources = BTreeMap::new(); + sources.insert("test.sol".to_owned(), EXTCODECOPY_TEST_SOURCE.to_owned()); + + super::build_solidity( + sources, + BTreeMap::new(), + None, + SolcPipeline::Yul, + era_compiler_llvm_context::OptimizerSettings::cycles(), + ) + .expect("Test failure"); +} + +pub const SELFDESTRUCT_TEST_SOURCE: &str = r#" +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +contract MinimalDestructible { + address payable public owner; + + constructor() { + owner = payable(msg.sender); + } + + function destroy() public { + require(msg.sender == owner, "Only the owner can call this function."); + selfdestruct(owner); + } +} + "#; + +#[test] +#[should_panic(expected = "The `SELFDESTRUCT` instruction is not supported")] +fn selfdestruct_evmla() { + let mut sources = BTreeMap::new(); + sources.insert("test.sol".to_owned(), SELFDESTRUCT_TEST_SOURCE.to_owned()); + + super::build_solidity( + sources, + BTreeMap::new(), + None, + SolcPipeline::EVMLA, + era_compiler_llvm_context::OptimizerSettings::cycles(), + ) + .expect("Test failure"); +} + +#[test] +#[should_panic(expected = "The `SELFDESTRUCT` instruction is not supported")] +fn selfdestruct_yul() { + let mut sources = BTreeMap::new(); + sources.insert("test.sol".to_owned(), SELFDESTRUCT_TEST_SOURCE.to_owned()); + + super::build_solidity( + sources, + BTreeMap::new(), + None, + SolcPipeline::Yul, + era_compiler_llvm_context::OptimizerSettings::cycles(), + ) + .expect("Test failure"); +} diff --git a/crates/solidity/src/warning.rs b/crates/solidity/src/warning.rs new file mode 100644 index 0000000..053900d --- /dev/null +++ b/crates/solidity/src/warning.rs @@ -0,0 +1,58 @@ +//! +//! The compiler warning. +//! + +use std::str::FromStr; + +use serde::Deserialize; +use serde::Serialize; + +/// +/// The compiler warning. +/// +#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Hash)] +pub enum Warning { + /// The warning for eponymous feature. + EcRecover, + /// The warning for eponymous feature. + SendTransfer, + /// The warning for eponymous feature. + ExtCodeSize, + /// The warning for eponymous feature. + TxOrigin, + /// The warning for eponymous feature. + BlockTimestamp, + /// The warning for eponymous feature. + BlockNumber, + /// The warning for eponymous feature. + BlockHash, +} + +impl Warning { + /// + /// Converts string arguments into an array of warnings. + /// + pub fn try_from_strings(strings: &[String]) -> Result, anyhow::Error> { + strings + .iter() + .map(|string| Self::from_str(string)) + .collect() + } +} + +impl FromStr for Warning { + type Err = anyhow::Error; + + fn from_str(string: &str) -> Result { + match string { + "ecrecover" => Ok(Self::EcRecover), + "sendtransfer" => Ok(Self::SendTransfer), + "extcodesize" => Ok(Self::ExtCodeSize), + "txorigin" => Ok(Self::TxOrigin), + "blocktimestamp" => Ok(Self::BlockTimestamp), + "blocknumber" => Ok(Self::BlockNumber), + "blockhash" => Ok(Self::BlockHash), + _ => Err(anyhow::anyhow!("Invalid warning: {}", string)), + } + } +} diff --git a/crates/solidity/src/yul/error.rs b/crates/solidity/src/yul/error.rs new file mode 100644 index 0000000..c1d7f46 --- /dev/null +++ b/crates/solidity/src/yul/error.rs @@ -0,0 +1,19 @@ +//! +//! The Yul IR error. +//! + +use crate::yul::lexer::error::Error as LexerError; +use crate::yul::parser::error::Error as ParserError; + +/// +/// The Yul IR error. +/// +#[derive(Debug, thiserror::Error, PartialEq, Eq)] +pub enum Error { + /// The lexer error. + #[error("Lexical error: {0}")] + Lexer(#[from] LexerError), + /// The parser error. + #[error("Syntax error: {0}")] + Parser(#[from] ParserError), +} diff --git a/crates/solidity/src/yul/lexer/error.rs b/crates/solidity/src/yul/lexer/error.rs new file mode 100644 index 0000000..13639c0 --- /dev/null +++ b/crates/solidity/src/yul/lexer/error.rs @@ -0,0 +1,20 @@ +//! +//! The Yul IR lexer error. +//! + +use crate::yul::lexer::token::location::Location; + +/// +/// The Yul IR lexer error. +/// +#[derive(Debug, thiserror::Error, PartialEq, Eq)] +pub enum Error { + /// The invalid lexeme error. + #[error("{location} Invalid character sequence `{sequence}`")] + InvalidLexeme { + /// The lexeme location. + location: Location, + /// The invalid sequence of characters. + sequence: String, + }, +} diff --git a/crates/solidity/src/yul/lexer/mod.rs b/crates/solidity/src/yul/lexer/mod.rs new file mode 100644 index 0000000..637a13a --- /dev/null +++ b/crates/solidity/src/yul/lexer/mod.rs @@ -0,0 +1,137 @@ +//! +//! The compiler lexer. +//! + +pub mod error; +pub mod token; + +#[cfg(test)] +mod tests; + +use self::error::Error; +use self::token::lexeme::comment::Comment; +use self::token::lexeme::identifier::Identifier; +use self::token::lexeme::literal::integer::Integer as IntegerLiteral; +use self::token::lexeme::literal::string::String as StringLiteral; +use self::token::lexeme::symbol::Symbol; +use self::token::lexeme::Lexeme; +use self::token::location::Location; +use self::token::Token; + +/// +/// The compiler lexer. +/// +pub struct Lexer { + /// The input source code. + input: String, + /// The number of characters processed so far. + offset: usize, + /// The current location. + location: Location, + /// The peeked lexeme, waiting to be fetched. + peeked: Option, +} + +impl Lexer { + /// + /// A shortcut constructor. + /// + pub fn new(mut input: String) -> Self { + input.push('\n'); + + Self { + input, + offset: 0, + location: Location::default(), + peeked: None, + } + } + + /// + /// Advances the lexer, returning the next lexeme. + /// + #[allow(clippy::should_implement_trait)] + pub fn next(&mut self) -> Result { + if let Some(peeked) = self.peeked.take() { + return Ok(peeked); + } + + while self.offset < self.input.len() { + let input = &self.input[self.offset..]; + + if input.starts_with(|character| char::is_ascii_whitespace(&character)) { + if input.starts_with('\n') { + self.location.line += 1; + self.location.column = 1; + } else if !input.starts_with('\r') { + self.location.column += 1; + } + self.offset += 1; + continue; + } + + if let Some(token) = Comment::parse(input) { + self.offset += token.length; + self.location + .shift_down(token.location.line, token.location.column); + continue; + } + + if let Some(mut token) = StringLiteral::parse(input) { + token.location = self.location; + + self.offset += token.length; + self.location.shift_right(token.length); + return Ok(token); + } + + if let Some(mut token) = IntegerLiteral::parse(input) { + token.location = self.location; + + self.offset += token.length; + self.location.shift_right(token.length); + return Ok(token); + } + + if let Some(mut token) = Identifier::parse(input) { + token.location = self.location; + + self.offset += token.length; + self.location.shift_right(token.length); + return Ok(token); + } + + if let Some(mut token) = Symbol::parse(input) { + token.location = self.location; + + self.offset += token.length; + self.location.shift_right(token.length); + return Ok(token); + } + + let end = self.input[self.offset..] + .find(char::is_whitespace) + .unwrap_or(self.input.len()); + return Err(Error::InvalidLexeme { + location: self.location, + sequence: self.input[self.offset..self.offset + end].to_owned(), + }); + } + + Ok(Token::new(self.location, Lexeme::EndOfFile, 0)) + } + + /// + /// Peeks the next lexeme without advancing the iterator. + /// + pub fn peek(&mut self) -> Result { + match self.peeked { + Some(ref peeked) => Ok(peeked.clone()), + None => { + let peeked = self.next()?; + self.peeked = Some(peeked.clone()); + Ok(peeked) + } + } + } +} diff --git a/crates/solidity/src/yul/lexer/tests.rs b/crates/solidity/src/yul/lexer/tests.rs new file mode 100644 index 0000000..5070edd --- /dev/null +++ b/crates/solidity/src/yul/lexer/tests.rs @@ -0,0 +1,91 @@ +//! +//! The Yul IR lexer tests. +//! + +use crate::yul::lexer::error::Error; +use crate::yul::lexer::token::lexeme::Lexeme; +use crate::yul::lexer::token::location::Location; +use crate::yul::lexer::Lexer; + +#[test] +fn default() { + let input = r#" +object "Test" { + code { + { + /* + The deploy code. + */ + mstore(64, 128) + if callvalue() { revert(0, 0) } + let _1 := datasize("Test_deployed") + codecopy(0, dataoffset("Test_deployed"), _1) + return(0, _1) + } + } + object "Test_deployed" { + code { + { + /* + The runtime code. + */ + mstore(64, 128) + if iszero(lt(calldatasize(), 4)) + { + let _1 := 0 + switch shr(224, calldataload(_1)) + case 0x3df4ddf4 { + if callvalue() { revert(_1, _1) } + if slt(add(calldatasize(), not(3)), _1) { revert(_1, _1) } + let memPos := allocate_memory(_1) + mstore(memPos, 0x2a) + return(memPos, 32) + } + case 0x5a8ac02d { + if callvalue() { revert(_1, _1) } + if slt(add(calldatasize(), not(3)), _1) { revert(_1, _1) } + let memPos_1 := allocate_memory(_1) + return(memPos_1, sub(abi_encode_uint256(memPos_1, 0x63), memPos_1)) + } + } + revert(0, 0) + } + function abi_encode_uint256(headStart, value0) -> tail + { + tail := add(headStart, 32) + mstore(headStart, value0) + } + function allocate_memory(size) -> memPtr + { + memPtr := mload(64) + let newFreePtr := add(memPtr, and(add(size, 31), not(31))) + if or(gt(newFreePtr, 0xffffffffffffffff)#, lt(newFreePtr, memPtr)) + { + mstore(0, shl(224, 0x4e487b71)) + mstore(4, 0x41) + revert(0, 0x24) + } + mstore(64, newFreePtr) + } + } + } +} + "#; + + let mut lexer = Lexer::new(input.to_owned()); + loop { + match lexer.next() { + Ok(token) => assert_ne!(token.lexeme, Lexeme::EndOfFile), + Err(error) => { + assert_eq!( + error, + Error::InvalidLexeme { + location: Location::new(51, 57), + sequence: "#,".to_owned(), + } + ); + break; + } + } + } +} diff --git a/crates/solidity/src/yul/lexer/token/lexeme/comment/mod.rs b/crates/solidity/src/yul/lexer/token/lexeme/comment/mod.rs new file mode 100644 index 0000000..607e59f --- /dev/null +++ b/crates/solidity/src/yul/lexer/token/lexeme/comment/mod.rs @@ -0,0 +1,38 @@ +//! +//! The comment lexeme. +//! + +pub mod multi_line; +pub mod single_line; + +use crate::yul::lexer::token::Token; + +use self::multi_line::Comment as MultiLineComment; +use self::single_line::Comment as SingleLineComment; + +/// +/// The comment lexeme. +/// +#[derive(Debug, Clone, PartialEq, Eq)] +#[allow(dead_code)] +pub enum Comment { + /// The single-line comment. + SingleLine(SingleLineComment), + /// The multi-line comment. + MultiLine(MultiLineComment), +} + +impl Comment { + /// + /// Returns the comment's length, including the trimmed whitespace around it. + /// + pub fn parse(input: &str) -> Option { + if input.starts_with(SingleLineComment::START) { + Some(SingleLineComment::parse(input)) + } else if input.starts_with(MultiLineComment::START) { + Some(MultiLineComment::parse(input)) + } else { + None + } + } +} diff --git a/crates/solidity/src/yul/lexer/token/lexeme/comment/multi_line.rs b/crates/solidity/src/yul/lexer/token/lexeme/comment/multi_line.rs new file mode 100644 index 0000000..12eea7a --- /dev/null +++ b/crates/solidity/src/yul/lexer/token/lexeme/comment/multi_line.rs @@ -0,0 +1,37 @@ +//! +//! The multi-line comment lexeme. +//! + +use crate::yul::lexer::token::lexeme::Lexeme; +use crate::yul::lexer::token::location::Location; +use crate::yul::lexer::token::Token; + +/// +/// The multi-line comment lexeme. +/// +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Comment {} + +impl Comment { + /// The start symbol. + pub const START: &'static str = "/*"; + /// The end symbol. + pub const END: &'static str = "*/"; + + /// + /// Returns the comment, including its length and number of lines. + /// + pub fn parse(input: &str) -> Token { + let end_position = input.find(Self::END).unwrap_or(input.len()); + let input = &input[..end_position]; + + let length = end_position + Self::END.len(); + let lines = input.matches('\n').count(); + let columns = match input.rfind('\n') { + Some(new_line) => end_position - (new_line + 1), + None => end_position, + }; + + Token::new(Location::new(lines, columns), Lexeme::Comment, length) + } +} diff --git a/crates/solidity/src/yul/lexer/token/lexeme/comment/single_line.rs b/crates/solidity/src/yul/lexer/token/lexeme/comment/single_line.rs new file mode 100644 index 0000000..ab788e0 --- /dev/null +++ b/crates/solidity/src/yul/lexer/token/lexeme/comment/single_line.rs @@ -0,0 +1,30 @@ +//! +//! The single-line comment lexeme. +//! + +use crate::yul::lexer::token::lexeme::Lexeme; +use crate::yul::lexer::token::location::Location; +use crate::yul::lexer::token::Token; + +/// +/// The single-line comment lexeme. +/// +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Comment {} + +impl Comment { + /// The start symbol. + pub const START: &'static str = "//"; + /// The end symbol. + pub const END: &'static str = "\n"; + + /// + /// Returns the comment's length, including the trimmed whitespace around it. + /// + pub fn parse(input: &str) -> Token { + let end_position = input.find(Self::END).unwrap_or(input.len()); + let length = end_position + Self::END.len(); + + Token::new(Location::new(1, 1), Lexeme::Comment, length) + } +} diff --git a/crates/solidity/src/yul/lexer/token/lexeme/identifier.rs b/crates/solidity/src/yul/lexer/token/lexeme/identifier.rs new file mode 100644 index 0000000..0d9ea6c --- /dev/null +++ b/crates/solidity/src/yul/lexer/token/lexeme/identifier.rs @@ -0,0 +1,80 @@ +//! +//! The identifier lexeme. +//! + +use crate::yul::lexer::token::lexeme::keyword::Keyword; +use crate::yul::lexer::token::lexeme::Lexeme; +use crate::yul::lexer::token::location::Location; +use crate::yul::lexer::token::Token; + +/// +/// The identifier lexeme. +/// +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Identifier { + /// The inner string. + pub inner: String, +} + +impl Identifier { + /// + /// A shortcut constructor. + /// + pub fn new(inner: String) -> Self { + Self { inner } + } + + /// + /// Parses the identifier, returning it as a token. + /// + pub fn parse(input: &str) -> Option { + if !input.starts_with(Self::can_begin) { + return None; + } + let end = input.find(Self::cannot_continue).unwrap_or(input.len()); + + let inner = input[..end].to_string(); + let length = inner.len(); + + if let Some(token) = Keyword::parse(inner.as_str()) { + return Some(token); + } + + Some(Token::new( + Location::new(0, length), + Lexeme::Identifier(Self::new(inner)), + length, + )) + } + + /// + /// Checks whether the character can begin an identifier. + /// + pub fn can_begin(character: char) -> bool { + character.is_alphabetic() || character == '_' || character == '$' + } + + /// + /// Checks whether the character can continue an identifier. + /// + pub fn can_continue(character: char) -> bool { + Self::can_begin(character) + || character.is_numeric() + || character == '_' + || character == '$' + || character == '.' + } + + /// + /// Checks whether the character cannot continue an identifier. + /// + pub fn cannot_continue(character: char) -> bool { + !Self::can_continue(character) + } +} + +impl std::fmt::Display for Identifier { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.inner) + } +} diff --git a/crates/solidity/src/yul/lexer/token/lexeme/keyword.rs b/crates/solidity/src/yul/lexer/token/lexeme/keyword.rs new file mode 100644 index 0000000..33c984e --- /dev/null +++ b/crates/solidity/src/yul/lexer/token/lexeme/keyword.rs @@ -0,0 +1,158 @@ +//! +//! The keyword lexeme. +//! + +use crate::yul::lexer::token::lexeme::literal::boolean::Boolean as BooleanLiteral; +use crate::yul::lexer::token::lexeme::literal::Literal; +use crate::yul::lexer::token::lexeme::Lexeme; +use crate::yul::lexer::token::location::Location; +use crate::yul::lexer::token::Token; + +/// +/// The keyword lexeme. +/// +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Keyword { + /// The `object` keyword. + Object, + /// The `code` keyword. + Code, + /// The `function` keyword. + Function, + /// The `let` keyword. + Let, + /// The `if` keyword. + If, + /// The `switch` keyword. + Switch, + /// The `case` keyword. + Case, + /// The `default` keyword. + Default, + /// The `for` keyword. + For, + /// The `break` keyword. + Break, + /// The `continue` keyword. + Continue, + /// The `leave` keyword. + Leave, + /// The `true` keyword. + True, + /// The `false` keyword. + False, + /// The `bool` keyword. + Bool, + /// The `int{N}` keyword. + Int(usize), + /// The `uint{N}` keyword. + Uint(usize), +} + +impl Keyword { + /// + /// Parses the keyword, returning it as a token. + /// + pub fn parse(input: &str) -> Option { + let keyword = Self::parse_keyword(input)?; + let lexeme = match BooleanLiteral::try_from(keyword) { + Ok(literal) => Lexeme::Literal(Literal::Boolean(literal)), + Err(keyword) => Lexeme::Keyword(keyword), + }; + + let length = lexeme.to_string().len(); + if length != input.len() { + return None; + } + + Some(Token::new(Location::new(0, length), lexeme, length)) + } + + /// + /// Parses the keyword itself. + /// + fn parse_keyword(input: &str) -> Option { + if !input.starts_with(Self::can_begin) { + return None; + } + let end = input.find(Self::cannot_continue).unwrap_or(input.len()); + let input = &input[..end]; + + if let Some(input) = input.strip_prefix("int") { + if let Ok(bitlength) = input.parse::() { + return Some(Self::Int(bitlength)); + } + } + + if let Some(input) = input.strip_prefix("uint") { + if let Ok(bitlength) = input.parse::() { + return Some(Self::Uint(bitlength)); + } + } + + Some(match input { + "object" => Self::Object, + "code" => Self::Code, + "function" => Self::Function, + "let" => Self::Let, + "if" => Self::If, + "switch" => Self::Switch, + "case" => Self::Case, + "default" => Self::Default, + "for" => Self::For, + "break" => Self::Break, + "continue" => Self::Continue, + "leave" => Self::Leave, + "true" => Self::True, + "false" => Self::False, + "bool" => Self::Bool, + + _ => return None, + }) + } + + /// + /// Checks whether the character can begin a keyword. + /// + pub fn can_begin(character: char) -> bool { + character.is_alphabetic() + } + + /// + /// Checks whether the character can continue a keyword. + /// + pub fn can_continue(character: char) -> bool { + Self::can_begin(character) || character.is_numeric() + } + + /// + /// Checks whether the character cannot continue a keyword. + /// + pub fn cannot_continue(character: char) -> bool { + !Self::can_continue(character) + } +} + +impl std::fmt::Display for Keyword { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Object => write!(f, "object"), + Self::Code => write!(f, "code"), + Self::Function => write!(f, "function"), + Self::Let => write!(f, "let"), + Self::If => write!(f, "if"), + Self::Switch => write!(f, "switch"), + Self::Case => write!(f, "case"), + Self::Default => write!(f, "default"), + Self::For => write!(f, "for"), + Self::Break => write!(f, "break"), + Self::Continue => write!(f, "continue"), + Self::Leave => write!(f, "leave"), + Self::True => write!(f, "true"), + Self::False => write!(f, "false"), + Self::Bool => write!(f, "bool"), + Self::Int(bitlength) => write!(f, "int{bitlength}"), + Self::Uint(bitlength) => write!(f, "uint{bitlength}"), + } + } +} diff --git a/crates/solidity/src/yul/lexer/token/lexeme/literal/boolean.rs b/crates/solidity/src/yul/lexer/token/lexeme/literal/boolean.rs new file mode 100644 index 0000000..5881542 --- /dev/null +++ b/crates/solidity/src/yul/lexer/token/lexeme/literal/boolean.rs @@ -0,0 +1,66 @@ +//! +//! The boolean literal lexeme. +//! + +use serde::Deserialize; +use serde::Serialize; + +use crate::yul::lexer::token::lexeme::keyword::Keyword; + +/// +/// The boolean literal lexeme. +/// +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +pub enum Boolean { + /// Created from the `false` keyword. + False, + /// Created from the `true` keyword. + True, +} + +impl Boolean { + /// + /// Creates a `false` value. + /// + pub fn r#false() -> Self { + Self::False + } + + /// + /// Creates a `true` value. + /// + pub fn r#true() -> Self { + Self::True + } +} + +impl TryFrom for Boolean { + type Error = Keyword; + + fn try_from(keyword: Keyword) -> Result { + Ok(match keyword { + Keyword::False => Self::False, + Keyword::True => Self::True, + unknown => return Err(unknown), + }) + } +} + +impl From for Boolean { + fn from(value: bool) -> Self { + if value { + Self::True + } else { + Self::False + } + } +} + +impl std::fmt::Display for Boolean { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::False => write!(f, "false"), + Self::True => write!(f, "true"), + } + } +} diff --git a/crates/solidity/src/yul/lexer/token/lexeme/literal/integer.rs b/crates/solidity/src/yul/lexer/token/lexeme/literal/integer.rs new file mode 100644 index 0000000..a28064c --- /dev/null +++ b/crates/solidity/src/yul/lexer/token/lexeme/literal/integer.rs @@ -0,0 +1,118 @@ +//! +//! The integer literal lexeme. +//! + +use serde::Deserialize; +use serde::Serialize; + +use crate::yul::lexer::token::lexeme::Lexeme; +use crate::yul::lexer::token::lexeme::Literal; +use crate::yul::lexer::token::location::Location; +use crate::yul::lexer::token::Token; + +/// +/// The integer literal lexeme. +/// +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +pub enum Integer { + /// An integer literal, like `42`. + Decimal { + /// The inner literal contents. + inner: String, + }, + /// A hexadecimal literal, like `0xffff`. + Hexadecimal { + /// The inner literal contents. + inner: String, + }, +} + +impl Integer { + /// + /// Creates a decimal value. + /// + pub fn new_decimal(inner: String) -> Self { + Self::Decimal { inner } + } + + /// + /// Creates a hexadecimal value. + /// + pub fn new_hexadecimal(inner: String) -> Self { + Self::Hexadecimal { inner } + } + + /// + /// Parses the value from the source code slice. + /// + pub fn parse(input: &str) -> Option { + let (value, length) = if let Some(body) = input.strip_prefix("0x") { + let end = body + .find(Self::cannot_continue_hexadecimal) + .unwrap_or(body.len()); + let length = "0x".len() + end; + let value = Self::new_hexadecimal(input[..length].to_owned()); + (value, length) + } else if input.starts_with(Self::can_begin_decimal) { + let end = input + .find(Self::cannot_continue_decimal) + .unwrap_or(input.len()); + let length = end; + let value = Self::new_decimal(input[..length].to_owned()); + (value, length) + } else { + return None; + }; + + let token = Token::new( + Location::new(0, length), + Lexeme::Literal(Literal::Integer(value)), + length, + ); + Some(token) + } + + /// + /// Checks whether the character can begin a decimal number. + /// + pub fn can_begin_decimal(character: char) -> bool { + Self::can_continue_decimal(character) + } + + /// + /// Checks whether the character can continue a decimal number. + /// + pub fn can_continue_decimal(character: char) -> bool { + character.is_digit(era_compiler_common::BASE_DECIMAL) + } + + /// + /// Checks whether the character cannot continue a decimal number. + /// + pub fn cannot_continue_decimal(character: char) -> bool { + !Self::can_continue_decimal(character) + } + + /// + /// Checks whether the character can continue a hexadecimal number. + /// + pub fn can_continue_hexadecimal(character: char) -> bool { + character.is_digit(era_compiler_common::BASE_HEXADECIMAL) + } + + /// + /// Checks whether the character cannot continue a hexadecimal number. + /// + pub fn cannot_continue_hexadecimal(character: char) -> bool { + !Self::can_continue_hexadecimal(character) + } +} + +impl std::fmt::Display for Integer { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Decimal { inner } => write!(f, "{inner}"), + Self::Hexadecimal { inner } => write!(f, "{inner}"), + } + } +} diff --git a/crates/solidity/src/yul/lexer/token/lexeme/literal/mod.rs b/crates/solidity/src/yul/lexer/token/lexeme/literal/mod.rs new file mode 100644 index 0000000..372dd79 --- /dev/null +++ b/crates/solidity/src/yul/lexer/token/lexeme/literal/mod.rs @@ -0,0 +1,37 @@ +//! +//! The literal lexeme. +//! + +pub mod boolean; +pub mod integer; +pub mod string; + +use serde::Deserialize; +use serde::Serialize; + +use self::boolean::Boolean; +use self::integer::Integer; +use self::string::String; + +/// +/// The literal lexeme. +/// +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +pub enum Literal { + /// A boolean literal, like `true`, or `false`. + Boolean(Boolean), + /// An integer literal, like `42`, or `0xff`. + Integer(Integer), + /// A string literal, like `"message"`. + String(String), +} + +impl std::fmt::Display for Literal { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Boolean(inner) => write!(f, "{inner}"), + Self::Integer(inner) => write!(f, "{inner}"), + Self::String(inner) => write!(f, "{inner}"), + } + } +} diff --git a/crates/solidity/src/yul/lexer/token/lexeme/literal/string.rs b/crates/solidity/src/yul/lexer/token/lexeme/literal/string.rs new file mode 100644 index 0000000..c50dc08 --- /dev/null +++ b/crates/solidity/src/yul/lexer/token/lexeme/literal/string.rs @@ -0,0 +1,93 @@ +//! +//! The string literal lexeme. +//! + +use serde::Deserialize; +use serde::Serialize; + +use crate::yul::lexer::token::lexeme::Lexeme; +use crate::yul::lexer::token::lexeme::Literal; +use crate::yul::lexer::token::location::Location; +use crate::yul::lexer::token::Token; + +/// +/// The string literal lexeme. +/// +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +pub struct String { + /// The inner string contents. + pub inner: std::string::String, + /// Whether the string is hexadecimal. + pub is_hexadecimal: bool, +} + +impl String { + /// + /// Creates a string literal value. + /// + pub fn new(inner: ::std::string::String, is_hexadecimal: bool) -> Self { + Self { + inner, + is_hexadecimal, + } + } + + /// + /// Parses the value from the source code slice. + /// + pub fn parse(input: &str) -> Option { + let mut length = 0; + + let is_string = input[length..].starts_with('"'); + let is_hex_string = input[length..].starts_with(r#"hex""#); + + if !is_string && !is_hex_string { + return None; + } + + if is_string { + length += 1; + } + if is_hex_string { + length += r#"hex""#.len(); + } + + let mut string = std::string::String::new(); + loop { + if input[length..].starts_with('\\') { + string.push(input.chars().nth(length).expect("Always exists")); + string.push(input.chars().nth(length + 1).expect("Always exists")); + length += 2; + continue; + } + + if input[length..].starts_with('"') { + length += 1; + break; + } + + string.push(input.chars().nth(length).expect("Always exists")); + length += 1; + } + + let string = string + .strip_prefix('"') + .and_then(|string| string.strip_suffix('"')) + .unwrap_or(string.as_str()) + .to_owned(); + + let literal = Self::new(string, is_hex_string); + + Some(Token::new( + Location::new(0, length), + Lexeme::Literal(Literal::String(literal)), + length, + )) + } +} + +impl std::fmt::Display for String { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.inner) + } +} diff --git a/crates/solidity/src/yul/lexer/token/lexeme/mod.rs b/crates/solidity/src/yul/lexer/token/lexeme/mod.rs new file mode 100644 index 0000000..60377f5 --- /dev/null +++ b/crates/solidity/src/yul/lexer/token/lexeme/mod.rs @@ -0,0 +1,46 @@ +//! +//! The lexeme. +//! + +pub mod comment; +pub mod identifier; +pub mod keyword; +pub mod literal; +pub mod symbol; + +use self::identifier::Identifier; +use self::keyword::Keyword; +use self::literal::Literal; +use self::symbol::Symbol; + +/// +/// The lexeme. +/// +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Lexeme { + /// The keyword lexeme. + Keyword(Keyword), + /// The symbol lexeme. + Symbol(Symbol), + /// The identifier lexeme. + Identifier(Identifier), + /// The literal lexeme. + Literal(Literal), + /// The comment lexeme. + Comment, + /// The end-of-file lexeme. + EndOfFile, +} + +impl std::fmt::Display for Lexeme { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Keyword(inner) => write!(f, "{inner}"), + Self::Symbol(inner) => write!(f, "{inner}"), + Self::Identifier(inner) => write!(f, "{inner}"), + Self::Literal(inner) => write!(f, "{inner}"), + Self::Comment => Ok(()), + Self::EndOfFile => write!(f, "EOF"), + } + } +} diff --git a/crates/solidity/src/yul/lexer/token/lexeme/symbol.rs b/crates/solidity/src/yul/lexer/token/lexeme/symbol.rs new file mode 100644 index 0000000..7185aa3 --- /dev/null +++ b/crates/solidity/src/yul/lexer/token/lexeme/symbol.rs @@ -0,0 +1,74 @@ +//! +//! The symbol lexeme. +//! + +use crate::yul::lexer::token::lexeme::Lexeme; +use crate::yul::lexer::token::location::Location; +use crate::yul::lexer::token::Token; + +/// +/// The symbol lexeme. +/// +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Symbol { + /// The `:=` symbol. + Assignment, + /// The `->` symbol. + Arrow, + /// The `{` symbol. + BracketCurlyLeft, + /// The `}` symbol. + BracketCurlyRight, + /// The `(` symbol. + ParenthesisLeft, + /// The `)` symbol. + ParenthesisRight, + /// The `,` symbol. + Comma, + /// The `:` symbol. + Colon, +} + +impl Symbol { + /// + /// Parses the symbol, returning it as a token. + /// + pub fn parse(input: &str) -> Option { + let (symbol, length) = match &input[..2] { + ":=" => (Self::Assignment, 2), + "->" => (Self::Arrow, 2), + + _ => match &input[..1] { + "{" => (Self::BracketCurlyLeft, 1), + "}" => (Self::BracketCurlyRight, 1), + "(" => (Self::ParenthesisLeft, 1), + ")" => (Self::ParenthesisRight, 1), + "," => (Self::Comma, 1), + ":" => (Self::Colon, 1), + + _ => return None, + }, + }; + + Some(Token::new( + Location::new(0, length), + Lexeme::Symbol(symbol), + length, + )) + } +} + +impl std::fmt::Display for Symbol { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Assignment => write!(f, ":="), + Self::Arrow => write!(f, "->"), + Self::BracketCurlyLeft => write!(f, "{{"), + Self::BracketCurlyRight => write!(f, "}}"), + Self::ParenthesisLeft => write!(f, "("), + Self::ParenthesisRight => write!(f, ")"), + Self::Comma => write!(f, ","), + Self::Colon => write!(f, ":"), + } + } +} diff --git a/crates/solidity/src/yul/lexer/token/location.rs b/crates/solidity/src/yul/lexer/token/location.rs new file mode 100644 index 0000000..2364bac --- /dev/null +++ b/crates/solidity/src/yul/lexer/token/location.rs @@ -0,0 +1,65 @@ +//! +//! The lexical token location. +//! + +use serde::Deserialize; +use serde::Serialize; + +/// +/// The token location in the source code file. +/// +#[derive(Debug, Serialize, Deserialize, Clone, Copy, Eq)] +pub struct Location { + /// The line number, starting from 1. + pub line: usize, + /// The column number, starting from 1. + pub column: usize, +} + +impl Default for Location { + fn default() -> Self { + Self { line: 1, column: 1 } + } +} + +impl Location { + /// + /// Creates a default location. + /// + pub fn new(line: usize, column: usize) -> Self { + Self { line, column } + } + + /// + /// Mutates the location by shifting the original one down by `lines` and + /// setting the column to `column`. + /// + pub fn shift_down(&mut self, lines: usize, column: usize) { + if lines == 0 { + self.shift_right(column); + return; + } + + self.line += lines; + self.column = column; + } + + /// + /// Mutates the location by shifting the original one rightward by `columns`. + /// + pub fn shift_right(&mut self, columns: usize) { + self.column += columns; + } +} + +impl PartialEq for Location { + fn eq(&self, other: &Self) -> bool { + self.line == other.line && self.column == other.column + } +} + +impl std::fmt::Display for Location { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}:{}", self.line, self.column) + } +} diff --git a/crates/solidity/src/yul/lexer/token/mod.rs b/crates/solidity/src/yul/lexer/token/mod.rs new file mode 100644 index 0000000..7d79db3 --- /dev/null +++ b/crates/solidity/src/yul/lexer/token/mod.rs @@ -0,0 +1,43 @@ +//! +//! The token. +//! + +pub mod lexeme; +pub mod location; + +use self::lexeme::Lexeme; +use self::location::Location; + +/// +/// The token. +/// +/// Contains a lexeme and its location. +/// +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Token { + /// The token location. + pub location: Location, + /// The lexeme. + pub lexeme: Lexeme, + /// The token length, including whitespaces. + pub length: usize, +} + +impl Token { + /// + /// A shortcut constructor. + /// + pub fn new(location: Location, lexeme: Lexeme, length: usize) -> Self { + Self { + location, + lexeme, + length, + } + } +} + +impl std::fmt::Display for Token { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}:{}", self.location, self.lexeme) + } +} diff --git a/crates/solidity/src/yul/mod.rs b/crates/solidity/src/yul/mod.rs new file mode 100644 index 0000000..8c22ec7 --- /dev/null +++ b/crates/solidity/src/yul/mod.rs @@ -0,0 +1,7 @@ +//! +//! The Yul IR compiling tools. +//! + +pub mod error; +pub mod lexer; +pub mod parser; diff --git a/crates/solidity/src/yul/parser/error.rs b/crates/solidity/src/yul/parser/error.rs new file mode 100644 index 0000000..a855c04 --- /dev/null +++ b/crates/solidity/src/yul/parser/error.rs @@ -0,0 +1,64 @@ +//! +//! The Yul IR parser error. +//! + +use std::collections::BTreeSet; + +use crate::yul::lexer::token::location::Location; + +/// +/// The Yul IR parser error. +/// +#[derive(Debug, thiserror::Error, PartialEq, Eq)] +pub enum Error { + /// An invalid token received from the lexer. + #[error("{location} Expected one of {expected:?}, found `{found}`")] + InvalidToken { + /// The invalid token location. + location: Location, + /// The list of expected tokens. + expected: Vec<&'static str>, + /// The invalid token. + found: String, + }, + /// A reserved keyword cannot be used as an identifier. + #[error("{location} The identifier `{identifier}` is reserved")] + ReservedIdentifier { + /// The invalid token location. + location: Location, + /// The invalid identifier. + identifier: String, + }, + /// Invalid number of function arguments. + #[error("{location} Function `{identifier}` must have {expected} arguments, found {found}")] + InvalidNumberOfArguments { + /// The invalid function location. + location: Location, + /// The invalid function name. + identifier: String, + /// The expected number of arguments. + expected: usize, + /// The actual number of arguments. + found: usize, + }, + /// Invalid object name. + #[error( + "{location} Objects must be named as '' (deploy) and '_deployed' (runtime)" + )] + InvalidObjectName { + /// The invalid token location. + location: Location, + /// The expected identifier. + expected: String, + /// The invalid identifier. + found: String, + }, + /// Invalid attributes. + #[error("{location} Found invalid LLVM attributes: {values:?}")] + InvalidAttributes { + /// The invalid token location. + location: Location, + /// The list of invalid attributes. + values: BTreeSet, + }, +} diff --git a/crates/solidity/src/yul/parser/identifier.rs b/crates/solidity/src/yul/parser/identifier.rs new file mode 100644 index 0000000..484bde2 --- /dev/null +++ b/crates/solidity/src/yul/parser/identifier.rs @@ -0,0 +1,127 @@ +//! +//! The YUL source code identifier. +//! + +use serde::Deserialize; +use serde::Serialize; + +use crate::yul::error::Error; +use crate::yul::lexer::token::lexeme::symbol::Symbol; +use crate::yul::lexer::token::lexeme::Lexeme; +use crate::yul::lexer::token::location::Location; +use crate::yul::lexer::token::Token; +use crate::yul::lexer::Lexer; +use crate::yul::parser::r#type::Type; + +/// +/// The YUL source code identifier. +/// +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +pub struct Identifier { + /// The location. + pub location: Location, + /// The inner string. + pub inner: String, + /// The type, if it has been explicitly specified. + pub r#type: Option, +} + +impl Identifier { + /// + /// A shortcut constructor. + /// + pub fn new(location: Location, inner: String) -> Self { + Self { + location, + inner, + r#type: None, + } + } + + /// + /// A shortcut constructor for a typed identifier. + /// + pub fn new_with_type(location: Location, inner: String, r#type: Option) -> Self { + Self { + location, + inner, + r#type, + } + } + + /// + /// Parses the identifier list where the types cannot be specified. + /// + pub fn parse_list( + lexer: &mut Lexer, + mut initial: Option, + ) -> Result<(Vec, Option), Error> { + let mut result = Vec::new(); + + let mut expected_comma = false; + loop { + let token = crate::yul::parser::take_or_next(initial.take(), lexer)?; + + match token { + Token { + location, + lexeme: Lexeme::Identifier(identifier), + .. + } if !expected_comma => { + result.push(Self::new(location, identifier.inner)); + expected_comma = true; + } + Token { + lexeme: Lexeme::Symbol(Symbol::Comma), + .. + } if expected_comma => { + expected_comma = false; + } + token => return Ok((result, Some(token))), + } + } + } + + /// + /// Parses the identifier list where the types may be optionally specified. + /// + pub fn parse_typed_list( + lexer: &mut Lexer, + mut initial: Option, + ) -> Result<(Vec, Option), Error> { + let mut result = Vec::new(); + + let mut expected_comma = false; + loop { + let token = crate::yul::parser::take_or_next(initial.take(), lexer)?; + + match token { + Token { + lexeme: Lexeme::Identifier(identifier), + location, + .. + } if !expected_comma => { + let r#type = match lexer.peek()? { + Token { + lexeme: Lexeme::Symbol(Symbol::Colon), + .. + } => { + lexer.next()?; + Some(Type::parse(lexer, None)?) + } + _ => None, + }; + result.push(Self::new_with_type(location, identifier.inner, r#type)); + expected_comma = true; + } + Token { + lexeme: Lexeme::Symbol(Symbol::Comma), + .. + } if expected_comma => { + expected_comma = false; + } + token => return Ok((result, Some(token))), + } + } + } +} diff --git a/crates/solidity/src/yul/parser/mod.rs b/crates/solidity/src/yul/parser/mod.rs new file mode 100644 index 0000000..47e2a8f --- /dev/null +++ b/crates/solidity/src/yul/parser/mod.rs @@ -0,0 +1,22 @@ +//! +//! The YUL code block. +//! + +pub mod error; +pub mod identifier; +pub mod statement; +pub mod r#type; + +use crate::yul::lexer::error::Error as LexerError; +use crate::yul::lexer::token::Token; +use crate::yul::lexer::Lexer; + +/// +/// Returns the `token` value if it is `Some(_)`, otherwise takes the next token from the `stream`. +/// +pub fn take_or_next(mut token: Option, lexer: &mut Lexer) -> Result { + match token.take() { + Some(token) => Ok(token), + None => lexer.next(), + } +} diff --git a/crates/solidity/src/yul/parser/statement/assignment.rs b/crates/solidity/src/yul/parser/statement/assignment.rs new file mode 100644 index 0000000..1e2e328 --- /dev/null +++ b/crates/solidity/src/yul/parser/statement/assignment.rs @@ -0,0 +1,185 @@ +//! +//! The assignment expression statement. +//! + +use std::collections::HashSet; + +use inkwell::types::BasicType; +use serde::Deserialize; +use serde::Serialize; + +use crate::yul::error::Error; +use crate::yul::lexer::token::lexeme::symbol::Symbol; +use crate::yul::lexer::token::lexeme::Lexeme; +use crate::yul::lexer::token::location::Location; +use crate::yul::lexer::token::Token; +use crate::yul::lexer::Lexer; +use crate::yul::parser::error::Error as ParserError; +use crate::yul::parser::identifier::Identifier; +use crate::yul::parser::statement::expression::Expression; + +/// +/// The Yul assignment expression statement. +/// +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +pub struct Assignment { + /// The location. + pub location: Location, + /// The variable bindings. + pub bindings: Vec, + /// The initializing expression. + pub initializer: Expression, +} + +impl Assignment { + /// + /// The element parser. + /// + pub fn parse(lexer: &mut Lexer, initial: Option) -> Result { + let token = crate::yul::parser::take_or_next(initial, lexer)?; + + let (location, identifier) = match token { + Token { + location, + lexeme: Lexeme::Identifier(identifier), + .. + } => (location, identifier), + token => { + return Err(ParserError::InvalidToken { + location: token.location, + expected: vec!["{identifier}"], + found: token.lexeme.to_string(), + } + .into()); + } + }; + let length = identifier.inner.len(); + + match lexer.peek()? { + Token { + lexeme: Lexeme::Symbol(Symbol::Assignment), + .. + } => { + lexer.next()?; + + Ok(Self { + location, + bindings: vec![Identifier::new(location, identifier.inner)], + initializer: Expression::parse(lexer, None)?, + }) + } + Token { + lexeme: Lexeme::Symbol(Symbol::Comma), + .. + } => { + let (identifiers, next) = Identifier::parse_list( + lexer, + Some(Token::new(location, Lexeme::Identifier(identifier), length)), + )?; + + match crate::yul::parser::take_or_next(next, lexer)? { + Token { + lexeme: Lexeme::Symbol(Symbol::Assignment), + .. + } => {} + token => { + return Err(ParserError::InvalidToken { + location: token.location, + expected: vec![":="], + found: token.lexeme.to_string(), + } + .into()); + } + } + + Ok(Self { + location, + bindings: identifiers, + initializer: Expression::parse(lexer, None)?, + }) + } + token => Err(ParserError::InvalidToken { + location: token.location, + expected: vec![":=", ","], + found: token.lexeme.to_string(), + } + .into()), + } + } + + /// + /// Get the list of missing deployable libraries. + /// + pub fn get_missing_libraries(&self) -> HashSet { + self.initializer.get_missing_libraries() + } +} + +impl era_compiler_llvm_context::EraVMWriteLLVM for Assignment +where + D: era_compiler_llvm_context::EraVMDependency + Clone, +{ + fn into_llvm( + mut self, + context: &mut era_compiler_llvm_context::EraVMContext, + ) -> anyhow::Result<()> { + let value = match self.initializer.into_llvm(context)? { + Some(value) => value, + None => return Ok(()), + }; + + if self.bindings.len() == 1 { + let identifier = self.bindings.remove(0); + let pointer = context + .current_function() + .borrow() + .get_stack_pointer(identifier.inner.as_str()) + .ok_or_else(|| { + anyhow::anyhow!( + "{} Assignment to an undeclared variable `{}`", + identifier.location, + identifier.inner, + ) + })?; + context.build_store(pointer, value.to_llvm())?; + return Ok(()); + } + + let llvm_type = value.to_llvm().into_struct_value().get_type(); + let tuple_pointer = context.build_alloca(llvm_type, "assignment_pointer"); + context.build_store(tuple_pointer, value.to_llvm())?; + + for (index, binding) in self.bindings.into_iter().enumerate() { + let field_pointer = context.build_gep( + tuple_pointer, + &[ + context.field_const(0), + context + .integer_type(era_compiler_common::BIT_LENGTH_X32) + .const_int(index as u64, false), + ], + context.field_type().as_basic_type_enum(), + format!("assignment_binding_{index}_gep_pointer").as_str(), + ); + + let binding_pointer = context + .current_function() + .borrow() + .get_stack_pointer(binding.inner.as_str()) + .ok_or_else(|| { + anyhow::anyhow!( + "{} Assignment to an undeclared variable `{}`", + binding.location, + binding.inner, + ) + })?; + let value = context.build_load( + field_pointer, + format!("assignment_binding_{index}_value").as_str(), + )?; + context.build_store(binding_pointer, value)?; + } + + Ok(()) + } +} diff --git a/crates/solidity/src/yul/parser/statement/block.rs b/crates/solidity/src/yul/parser/statement/block.rs new file mode 100644 index 0000000..9f9a6ad --- /dev/null +++ b/crates/solidity/src/yul/parser/statement/block.rs @@ -0,0 +1,285 @@ +//! +//! The source code block. +//! + +use std::collections::HashSet; + +use serde::Deserialize; +use serde::Serialize; + +use crate::yul::error::Error; +use crate::yul::lexer::token::lexeme::symbol::Symbol; +use crate::yul::lexer::token::lexeme::Lexeme; +use crate::yul::lexer::token::location::Location; +use crate::yul::lexer::token::Token; +use crate::yul::lexer::Lexer; +use crate::yul::parser::error::Error as ParserError; +use crate::yul::parser::statement::assignment::Assignment; +use crate::yul::parser::statement::expression::Expression; +use crate::yul::parser::statement::Statement; + +/// +/// The Yul source code block. +/// +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +pub struct Block { + /// The location. + pub location: Location, + /// The block statements. + pub statements: Vec, +} + +impl Block { + /// + /// The element parser. + /// + pub fn parse(lexer: &mut Lexer, initial: Option) -> Result { + let token = crate::yul::parser::take_or_next(initial, lexer)?; + + let mut statements = Vec::new(); + + let location = match token { + Token { + lexeme: Lexeme::Symbol(Symbol::BracketCurlyLeft), + location, + .. + } => location, + token => { + return Err(ParserError::InvalidToken { + location: token.location, + expected: vec!["{"], + found: token.lexeme.to_string(), + } + .into()); + } + }; + + let mut remaining = None; + + loop { + match crate::yul::parser::take_or_next(remaining.take(), lexer)? { + token @ Token { + lexeme: Lexeme::Keyword(_), + .. + } => { + let (statement, next) = Statement::parse(lexer, Some(token))?; + remaining = next; + statements.push(statement); + } + token @ Token { + lexeme: Lexeme::Literal(_), + .. + } => { + statements + .push(Expression::parse(lexer, Some(token)).map(Statement::Expression)?); + } + token @ Token { + lexeme: Lexeme::Identifier(_), + .. + } => match lexer.peek()? { + Token { + lexeme: Lexeme::Symbol(Symbol::Assignment), + .. + } => { + statements.push( + Assignment::parse(lexer, Some(token)).map(Statement::Assignment)?, + ); + } + Token { + lexeme: Lexeme::Symbol(Symbol::Comma), + .. + } => { + statements.push( + Assignment::parse(lexer, Some(token)).map(Statement::Assignment)?, + ); + } + _ => { + statements.push( + Expression::parse(lexer, Some(token)).map(Statement::Expression)?, + ); + } + }, + token @ Token { + lexeme: Lexeme::Symbol(Symbol::BracketCurlyLeft), + .. + } => statements.push(Block::parse(lexer, Some(token)).map(Statement::Block)?), + Token { + lexeme: Lexeme::Symbol(Symbol::BracketCurlyRight), + .. + } => break, + token => { + return Err(ParserError::InvalidToken { + location: token.location, + expected: vec!["{keyword}", "{expression}", "{identifier}", "{", "}"], + found: token.lexeme.to_string(), + } + .into()); + } + } + } + + Ok(Self { + location, + statements, + }) + } + + /// + /// Get the list of missing deployable libraries. + /// + pub fn get_missing_libraries(&self) -> HashSet { + let mut libraries = HashSet::new(); + for statement in self.statements.iter() { + libraries.extend(statement.get_missing_libraries()); + } + libraries + } +} + +impl era_compiler_llvm_context::EraVMWriteLLVM for Block +where + D: era_compiler_llvm_context::EraVMDependency + Clone, +{ + fn into_llvm( + self, + context: &mut era_compiler_llvm_context::EraVMContext, + ) -> anyhow::Result<()> { + let current_function = context.current_function().borrow().name().to_owned(); + let current_block = context.basic_block(); + + let mut functions = Vec::with_capacity(self.statements.len()); + let mut local_statements = Vec::with_capacity(self.statements.len()); + + for statement in self.statements.into_iter() { + match statement { + Statement::FunctionDefinition(mut statement) => { + statement.declare(context)?; + functions.push(statement); + } + statement => local_statements.push(statement), + } + } + + for function in functions.into_iter() { + function.into_llvm(context)?; + } + + context.set_current_function(current_function.as_str())?; + context.set_basic_block(current_block); + for statement in local_statements.into_iter() { + if context.basic_block().get_terminator().is_some() { + break; + } + + match statement { + Statement::Block(block) => { + block.into_llvm(context)?; + } + Statement::Expression(expression) => { + expression.into_llvm(context)?; + } + Statement::VariableDeclaration(statement) => statement.into_llvm(context)?, + Statement::Assignment(statement) => statement.into_llvm(context)?, + Statement::IfConditional(statement) => statement.into_llvm(context)?, + Statement::Switch(statement) => statement.into_llvm(context)?, + Statement::ForLoop(statement) => statement.into_llvm(context)?, + Statement::Continue(_location) => { + context.build_unconditional_branch(context.r#loop().continue_block); + break; + } + Statement::Break(_location) => { + context.build_unconditional_branch(context.r#loop().join_block); + break; + } + Statement::Leave(_location) => { + context.build_unconditional_branch( + context.current_function().borrow().return_block(), + ); + break; + } + statement => anyhow::bail!( + "{} Unexpected local statement: {:?}", + statement.location(), + statement + ), + } + } + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use crate::yul::lexer::token::location::Location; + use crate::yul::lexer::Lexer; + use crate::yul::parser::error::Error; + use crate::yul::parser::statement::object::Object; + + #[test] + fn error_invalid_token_bracket_curly_left() { + let input = r#" +object "Test" { + code { + { + return(0, 0) + } + } + object "Test_deployed" { + code { + { + ( + return(0, 0) + } + } + } + } +} + "#; + + let mut lexer = Lexer::new(input.to_owned()); + let result = Object::parse(&mut lexer, None); + assert_eq!( + result, + Err(Error::InvalidToken { + location: Location::new(11, 17), + expected: vec!["{keyword}", "{expression}", "{identifier}", "{", "}"], + found: "(".to_owned(), + } + .into()) + ); + } + + #[test] + fn error_invalid_token_statement() { + let input = r#" +object "Test" { + code { + { + return(0, 0) + } + } + object "Test_deployed" { + code { + { + := + return(0, 0) + } + } + } +} + "#; + + let mut lexer = Lexer::new(input.to_owned()); + let result = Object::parse(&mut lexer, None); + assert_eq!( + result, + Err(Error::InvalidToken { + location: Location::new(11, 17), + expected: vec!["{keyword}", "{expression}", "{identifier}", "{", "}"], + found: ":=".to_owned(), + } + .into()) + ); + } +} diff --git a/crates/solidity/src/yul/parser/statement/code.rs b/crates/solidity/src/yul/parser/statement/code.rs new file mode 100644 index 0000000..1b4232e --- /dev/null +++ b/crates/solidity/src/yul/parser/statement/code.rs @@ -0,0 +1,118 @@ +//! +//! The YUL code. +//! + +use std::collections::HashSet; + +use serde::Deserialize; +use serde::Serialize; + +use crate::yul::error::Error; +use crate::yul::lexer::token::lexeme::keyword::Keyword; +use crate::yul::lexer::token::lexeme::Lexeme; +use crate::yul::lexer::token::location::Location; +use crate::yul::lexer::token::Token; +use crate::yul::lexer::Lexer; +use crate::yul::parser::error::Error as ParserError; +use crate::yul::parser::statement::block::Block; + +/// +/// The YUL code entity, which is the first block of the object. +/// +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +pub struct Code { + /// The location. + pub location: Location, + /// The main block. + pub block: Block, +} + +impl Code { + /// + /// The element parser. + /// + pub fn parse(lexer: &mut Lexer, initial: Option) -> Result { + let token = crate::yul::parser::take_or_next(initial, lexer)?; + + let location = match token { + Token { + lexeme: Lexeme::Keyword(Keyword::Code), + location, + .. + } => location, + token => { + return Err(ParserError::InvalidToken { + location: token.location, + expected: vec!["code"], + found: token.lexeme.to_string(), + } + .into()); + } + }; + + let block = Block::parse(lexer, None)?; + + Ok(Self { location, block }) + } + + /// + /// Get the list of missing deployable libraries. + /// + pub fn get_missing_libraries(&self) -> HashSet { + self.block.get_missing_libraries() + } +} + +impl era_compiler_llvm_context::EraVMWriteLLVM for Code +where + D: era_compiler_llvm_context::EraVMDependency + Clone, +{ + fn into_llvm( + self, + context: &mut era_compiler_llvm_context::EraVMContext, + ) -> anyhow::Result<()> { + self.block.into_llvm(context)?; + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use crate::yul::lexer::token::location::Location; + use crate::yul::lexer::Lexer; + use crate::yul::parser::error::Error; + use crate::yul::parser::statement::object::Object; + + #[test] + fn error_invalid_token_code() { + let input = r#" +object "Test" { + data { + { + return(0, 0) + } + } + object "Test_deployed" { + code { + { + return(0, 0) + } + } + } +} + "#; + + let mut lexer = Lexer::new(input.to_owned()); + let result = Object::parse(&mut lexer, None); + assert_eq!( + result, + Err(Error::InvalidToken { + location: Location::new(3, 5), + expected: vec!["code"], + found: "data".to_owned(), + } + .into()) + ); + } +} diff --git a/crates/solidity/src/yul/parser/statement/expression/function_call/mod.rs b/crates/solidity/src/yul/parser/statement/expression/function_call/mod.rs new file mode 100644 index 0000000..c7ef2f3 --- /dev/null +++ b/crates/solidity/src/yul/parser/statement/expression/function_call/mod.rs @@ -0,0 +1,1471 @@ +//! +//! The function call subexpression. +//! + +pub mod name; +pub mod verbatim; + +use std::collections::HashSet; + +use inkwell::values::BasicValue; +use serde::Deserialize; +use serde::Serialize; + +use crate::yul::error::Error; +use crate::yul::lexer::token::lexeme::literal::Literal as LexicalLiteral; +use crate::yul::lexer::token::lexeme::symbol::Symbol; +use crate::yul::lexer::token::lexeme::Lexeme; +use crate::yul::lexer::token::location::Location; +use crate::yul::lexer::token::Token; +use crate::yul::lexer::Lexer; +use crate::yul::parser::error::Error as ParserError; +use crate::yul::parser::statement::expression::literal::Literal; +use crate::yul::parser::statement::expression::Expression; + +use self::name::Name; + +/// +/// The Yul function call subexpression. +/// +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +pub struct FunctionCall { + /// The location. + pub location: Location, + /// The function name. + pub name: Name, + /// The function arguments expression list. + pub arguments: Vec, +} + +impl FunctionCall { + /// + /// The element parser. + /// + pub fn parse(lexer: &mut Lexer, initial: Option) -> Result { + let token = crate::yul::parser::take_or_next(initial, lexer)?; + + let (location, name) = match token { + Token { + lexeme: Lexeme::Identifier(identifier), + location, + .. + } => (location, Name::from(identifier.inner.as_str())), + token => { + return Err(ParserError::InvalidToken { + location: token.location, + expected: vec!["{identifier}"], + found: token.lexeme.to_string(), + } + .into()); + } + }; + + let mut arguments = Vec::new(); + loop { + let argument = match lexer.next()? { + Token { + lexeme: Lexeme::Symbol(Symbol::ParenthesisRight), + .. + } => break, + token => Expression::parse(lexer, Some(token))?, + }; + + arguments.push(argument); + + match lexer.peek()? { + Token { + lexeme: Lexeme::Symbol(Symbol::Comma), + .. + } => { + lexer.next()?; + continue; + } + Token { + lexeme: Lexeme::Symbol(Symbol::ParenthesisRight), + .. + } => { + lexer.next()?; + break; + } + _ => break, + } + } + + Ok(Self { + location, + name, + arguments, + }) + } + + /// + /// Get the list of missing deployable libraries. + /// + pub fn get_missing_libraries(&self) -> HashSet { + let mut libraries = HashSet::new(); + + if let Name::LinkerSymbol = self.name { + let _argument = self.arguments.first().expect("Always exists"); + if let Expression::Literal(Literal { + inner: LexicalLiteral::String(library_path), + .. + }) = self.arguments.first().expect("Always exists") + { + libraries.insert(library_path.to_string()); + } + return libraries; + } + + for argument in self.arguments.iter() { + libraries.extend(argument.get_missing_libraries()); + } + libraries + } + + /// + /// Converts the function call into an LLVM value. + /// + pub fn into_llvm<'ctx, D>( + mut self, + context: &mut era_compiler_llvm_context::EraVMContext<'ctx, D>, + ) -> anyhow::Result>> + where + D: era_compiler_llvm_context::EraVMDependency + Clone, + { + let location = self.location; + + match self.name { + Name::UserDefined(name) + if name.starts_with( + era_compiler_llvm_context::EraVMFunction::ZKSYNC_NEAR_CALL_ABI_PREFIX, + ) && context.is_system_mode() => + { + unimplemented!(); + } + Name::UserDefined(name) => { + let mut values = Vec::with_capacity(self.arguments.len()); + for argument in self.arguments.into_iter().rev() { + let value = argument.into_llvm(context)?.expect("Always exists").value; + values.push(value); + } + values.reverse(); + let function = context.get_function(name.as_str()).ok_or_else(|| { + anyhow::anyhow!("{} Undeclared function `{}`", location, name) + })?; + + let expected_arguments_count = + function.borrow().declaration().value.count_params() as usize; + if expected_arguments_count != values.len() { + anyhow::bail!( + "{} Function `{}` expected {} arguments, found {}", + location, + name, + expected_arguments_count, + values.len() + ); + } + + let return_value = context.build_invoke( + function.borrow().declaration(), + values.as_slice(), + format!("{name}_call").as_str(), + ); + + Ok(return_value) + } + + Name::Add => { + let arguments = self.pop_arguments_llvm::(context)?; + era_compiler_llvm_context::eravm_evm_arithmetic::addition( + context, + arguments[0].into_int_value(), + arguments[1].into_int_value(), + ) + .map(Some) + } + Name::Sub => { + let arguments = self.pop_arguments_llvm::(context)?; + era_compiler_llvm_context::eravm_evm_arithmetic::subtraction( + context, + arguments[0].into_int_value(), + arguments[1].into_int_value(), + ) + .map(Some) + } + Name::Mul => { + let arguments = self.pop_arguments_llvm::(context)?; + era_compiler_llvm_context::eravm_evm_arithmetic::multiplication( + context, + arguments[0].into_int_value(), + arguments[1].into_int_value(), + ) + .map(Some) + } + Name::Div => { + let arguments = self.pop_arguments_llvm::(context)?; + era_compiler_llvm_context::eravm_evm_arithmetic::division( + context, + arguments[0].into_int_value(), + arguments[1].into_int_value(), + ) + .map(Some) + } + Name::Mod => { + let arguments = self.pop_arguments_llvm::(context)?; + era_compiler_llvm_context::eravm_evm_arithmetic::remainder( + context, + arguments[0].into_int_value(), + arguments[1].into_int_value(), + ) + .map(Some) + } + Name::Sdiv => { + let arguments = self.pop_arguments_llvm::(context)?; + era_compiler_llvm_context::eravm_evm_arithmetic::division_signed( + context, + arguments[0].into_int_value(), + arguments[1].into_int_value(), + ) + .map(Some) + } + Name::Smod => { + let arguments = self.pop_arguments_llvm::(context)?; + era_compiler_llvm_context::eravm_evm_arithmetic::remainder_signed( + context, + arguments[0].into_int_value(), + arguments[1].into_int_value(), + ) + .map(Some) + } + + Name::Lt => { + let arguments = self.pop_arguments_llvm::(context)?; + era_compiler_llvm_context::eravm_evm_comparison::compare( + context, + arguments[0].into_int_value(), + arguments[1].into_int_value(), + inkwell::IntPredicate::ULT, + ) + .map(Some) + } + Name::Gt => { + let arguments = self.pop_arguments_llvm::(context)?; + era_compiler_llvm_context::eravm_evm_comparison::compare( + context, + arguments[0].into_int_value(), + arguments[1].into_int_value(), + inkwell::IntPredicate::UGT, + ) + .map(Some) + } + Name::Eq => { + let arguments = self.pop_arguments_llvm::(context)?; + era_compiler_llvm_context::eravm_evm_comparison::compare( + context, + arguments[0].into_int_value(), + arguments[1].into_int_value(), + inkwell::IntPredicate::EQ, + ) + .map(Some) + } + Name::IsZero => { + let arguments = self.pop_arguments_llvm::(context)?; + era_compiler_llvm_context::eravm_evm_comparison::compare( + context, + arguments[0].into_int_value(), + context.field_const(0), + inkwell::IntPredicate::EQ, + ) + .map(Some) + } + Name::Slt => { + let arguments = self.pop_arguments_llvm::(context)?; + era_compiler_llvm_context::eravm_evm_comparison::compare( + context, + arguments[0].into_int_value(), + arguments[1].into_int_value(), + inkwell::IntPredicate::SLT, + ) + .map(Some) + } + Name::Sgt => { + let arguments = self.pop_arguments_llvm::(context)?; + era_compiler_llvm_context::eravm_evm_comparison::compare( + context, + arguments[0].into_int_value(), + arguments[1].into_int_value(), + inkwell::IntPredicate::SGT, + ) + .map(Some) + } + + Name::Or => { + let arguments = self.pop_arguments_llvm::(context)?; + era_compiler_llvm_context::eravm_evm_bitwise::or( + context, + arguments[0].into_int_value(), + arguments[1].into_int_value(), + ) + .map(Some) + } + Name::Xor => { + let arguments = self.pop_arguments_llvm::(context)?; + era_compiler_llvm_context::eravm_evm_bitwise::xor( + context, + arguments[0].into_int_value(), + arguments[1].into_int_value(), + ) + .map(Some) + } + Name::Not => { + let arguments = self.pop_arguments_llvm::(context)?; + era_compiler_llvm_context::eravm_evm_bitwise::xor( + context, + arguments[0].into_int_value(), + context.field_type().const_all_ones(), + ) + .map(Some) + } + Name::And => { + let arguments = self.pop_arguments_llvm::(context)?; + era_compiler_llvm_context::eravm_evm_bitwise::and( + context, + arguments[0].into_int_value(), + arguments[1].into_int_value(), + ) + .map(Some) + } + Name::Shl => { + let arguments = self.pop_arguments_llvm::(context)?; + era_compiler_llvm_context::eravm_evm_bitwise::shift_left( + context, + arguments[0].into_int_value(), + arguments[1].into_int_value(), + ) + .map(Some) + } + Name::Shr => { + let arguments = self.pop_arguments_llvm::(context)?; + era_compiler_llvm_context::eravm_evm_bitwise::shift_right( + context, + arguments[0].into_int_value(), + arguments[1].into_int_value(), + ) + .map(Some) + } + Name::Sar => { + let arguments = self.pop_arguments_llvm::(context)?; + era_compiler_llvm_context::eravm_evm_bitwise::shift_right_arithmetic( + context, + arguments[0].into_int_value(), + arguments[1].into_int_value(), + ) + .map(Some) + } + Name::Byte => { + let arguments = self.pop_arguments_llvm::(context)?; + era_compiler_llvm_context::eravm_evm_bitwise::byte( + context, + arguments[0].into_int_value(), + arguments[1].into_int_value(), + ) + .map(Some) + } + Name::Pop => { + let _arguments = self.pop_arguments_llvm::(context)?; + Ok(None) + } + + Name::AddMod => { + let arguments = self.pop_arguments_llvm::(context)?; + era_compiler_llvm_context::eravm_evm_math::add_mod( + context, + arguments[0].into_int_value(), + arguments[1].into_int_value(), + arguments[2].into_int_value(), + ) + .map(Some) + } + Name::MulMod => { + let arguments = self.pop_arguments_llvm::(context)?; + era_compiler_llvm_context::eravm_evm_math::mul_mod( + context, + arguments[0].into_int_value(), + arguments[1].into_int_value(), + arguments[2].into_int_value(), + ) + .map(Some) + } + Name::Exp => { + let arguments = self.pop_arguments_llvm::(context)?; + era_compiler_llvm_context::eravm_evm_math::exponent( + context, + arguments[0].into_int_value(), + arguments[1].into_int_value(), + ) + .map(Some) + } + Name::SignExtend => { + let arguments = self.pop_arguments_llvm::(context)?; + era_compiler_llvm_context::eravm_evm_math::sign_extend( + context, + arguments[0].into_int_value(), + arguments[1].into_int_value(), + ) + .map(Some) + } + + Name::Keccak256 => { + let arguments = self.pop_arguments_llvm::(context)?; + era_compiler_llvm_context::eravm_evm_crypto::sha3( + context, + arguments[0].into_int_value(), + arguments[1].into_int_value(), + ) + .map(Some) + } + + Name::MLoad => { + let arguments = self.pop_arguments_llvm::(context)?; + era_compiler_llvm_context::eravm_evm_memory::load( + context, + arguments[0].into_int_value(), + ) + .map(Some) + } + Name::MStore => { + let arguments = self.pop_arguments_llvm::(context)?; + era_compiler_llvm_context::eravm_evm_memory::store( + context, + arguments[0].into_int_value(), + arguments[1].into_int_value(), + ) + .map(|_| None) + } + Name::MStore8 => { + let arguments = self.pop_arguments_llvm::(context)?; + era_compiler_llvm_context::eravm_evm_memory::store_byte( + context, + arguments[0].into_int_value(), + arguments[1].into_int_value(), + ) + .map(|_| None) + } + Name::MCopy => { + let arguments = self.pop_arguments_llvm::(context)?; + let destination = era_compiler_llvm_context::EraVMPointer::new_with_offset( + context, + era_compiler_llvm_context::EraVMAddressSpace::Heap, + context.byte_type(), + arguments[0].into_int_value(), + "mcopy_destination", + ); + let source = era_compiler_llvm_context::EraVMPointer::new_with_offset( + context, + era_compiler_llvm_context::EraVMAddressSpace::Heap, + context.byte_type(), + arguments[1].into_int_value(), + "mcopy_source", + ); + + context.build_memcpy( + context.intrinsics().memory_copy, + destination, + source, + arguments[2].into_int_value(), + "mcopy_size", + )?; + Ok(None) + } + + Name::SLoad => { + let arguments = self.pop_arguments_llvm::(context)?; + era_compiler_llvm_context::eravm_evm_storage::load( + context, + arguments[0].into_int_value(), + ) + .map(Some) + } + Name::SStore => { + let arguments = self.pop_arguments_llvm::(context)?; + era_compiler_llvm_context::eravm_evm_storage::store( + context, + arguments[0].into_int_value(), + arguments[1].into_int_value(), + ) + .map(|_| None) + } + Name::TLoad => { + let _arguments = self.pop_arguments_llvm::(context)?; + anyhow::bail!( + "{} The `TLOAD` instruction is not supported until zkVM v1.5.0", + location + ); + } + Name::TStore => { + let _arguments = self.pop_arguments_llvm::(context)?; + anyhow::bail!( + "{} The `TSTORE` instruction is not supported until zkVM v1.5.0", + location + ); + } + Name::LoadImmutable => todo!(), + Name::SetImmutable => { + let mut arguments = self.pop_arguments::(context)?; + let key = arguments[1].original.take().ok_or_else(|| { + anyhow::anyhow!("{} `load_immutable` literal is missing", location) + })?; + + if key.as_str() == "library_deploy_address" { + return Ok(None); + } + + let offset = context.solidity_mut().allocate_immutable(key.as_str()); + + let index = context.field_const(offset as u64); + let value = arguments[2].value.into_int_value(); + era_compiler_llvm_context::eravm_evm_immutable::store(context, index, value) + .map(|_| None) + } + + Name::CallDataLoad => { + let arguments = self.pop_arguments_llvm::(context)?; + + match context + .code_type() + .ok_or_else(|| anyhow::anyhow!("The contract code part type is undefined"))? + { + era_compiler_llvm_context::EraVMCodeType::Deploy => { + Ok(Some(context.field_const(0).as_basic_value_enum())) + } + era_compiler_llvm_context::EraVMCodeType::Runtime => { + era_compiler_llvm_context::eravm_evm_calldata::load( + context, + arguments[0].into_int_value(), + ) + .map(Some) + } + } + } + Name::CallDataSize => { + match context + .code_type() + .ok_or_else(|| anyhow::anyhow!("The contract code part type is undefined"))? + { + era_compiler_llvm_context::EraVMCodeType::Deploy => { + Ok(Some(context.field_const(0).as_basic_value_enum())) + } + era_compiler_llvm_context::EraVMCodeType::Runtime => { + era_compiler_llvm_context::eravm_evm_calldata::size(context).map(Some) + } + } + } + Name::CallDataCopy => { + let arguments = self.pop_arguments_llvm::(context)?; + + match context + .code_type() + .ok_or_else(|| anyhow::anyhow!("The contract code part type is undefined"))? + { + era_compiler_llvm_context::EraVMCodeType::Deploy => { + let calldata_size = + era_compiler_llvm_context::eravm_evm_calldata::size(context)?; + + era_compiler_llvm_context::eravm_evm_calldata::copy( + context, + arguments[0].into_int_value(), + calldata_size.into_int_value(), + arguments[2].into_int_value(), + ) + .map(|_| None) + } + era_compiler_llvm_context::EraVMCodeType::Runtime => { + era_compiler_llvm_context::eravm_evm_calldata::copy( + context, + arguments[0].into_int_value(), + arguments[1].into_int_value(), + arguments[2].into_int_value(), + ) + .map(|_| None) + } + } + } + Name::CodeSize => { + match context + .code_type() + .ok_or_else(|| anyhow::anyhow!("The contract code part type is undefined"))? + { + era_compiler_llvm_context::EraVMCodeType::Deploy => { + era_compiler_llvm_context::eravm_evm_calldata::size(context).map(Some) + } + era_compiler_llvm_context::EraVMCodeType::Runtime => { + let code_source = + era_compiler_llvm_context::eravm_general::code_source(context)?; + era_compiler_llvm_context::eravm_evm_ext_code::size( + context, + code_source.into_int_value(), + ) + .map(Some) + } + } + } + Name::CodeCopy => { + if let era_compiler_llvm_context::EraVMCodeType::Runtime = context + .code_type() + .ok_or_else(|| anyhow::anyhow!("The contract code part type is undefined"))? + { + anyhow::bail!( + "{} The `CODECOPY` instruction is not supported in the runtime code", + location, + ); + } + + let arguments = self.pop_arguments_llvm::(context)?; + era_compiler_llvm_context::eravm_evm_calldata::copy( + context, + arguments[0].into_int_value(), + arguments[1].into_int_value(), + arguments[2].into_int_value(), + ) + .map(|_| None) + } + Name::ReturnDataSize => { + era_compiler_llvm_context::eravm_evm_return_data::size(context).map(Some) + } + Name::ReturnDataCopy => { + let arguments = self.pop_arguments_llvm::(context)?; + era_compiler_llvm_context::eravm_evm_return_data::copy( + context, + arguments[0].into_int_value(), + arguments[1].into_int_value(), + arguments[2].into_int_value(), + ) + .map(|_| None) + } + Name::ExtCodeSize => { + let arguments = self.pop_arguments_llvm::(context)?; + era_compiler_llvm_context::eravm_evm_ext_code::size( + context, + arguments[0].into_int_value(), + ) + .map(Some) + } + Name::ExtCodeHash => { + let arguments = self.pop_arguments_llvm::(context)?; + era_compiler_llvm_context::eravm_evm_ext_code::hash( + context, + arguments[0].into_int_value(), + ) + .map(Some) + } + + Name::Return => { + let arguments = self.pop_arguments_llvm::(context)?; + era_compiler_llvm_context::eravm_evm_return::r#return( + context, + arguments[0].into_int_value(), + arguments[1].into_int_value(), + ) + .map(|_| None) + } + Name::Revert => { + let arguments = self.pop_arguments_llvm::(context)?; + era_compiler_llvm_context::eravm_evm_return::revert( + context, + arguments[0].into_int_value(), + arguments[1].into_int_value(), + ) + .map(|_| None) + } + Name::Stop => era_compiler_llvm_context::eravm_evm_return::stop(context).map(|_| None), + Name::Invalid => { + era_compiler_llvm_context::eravm_evm_return::invalid(context).map(|_| None) + } + + Name::Log0 => { + let arguments = self.pop_arguments_llvm::(context)?; + era_compiler_llvm_context::eravm_evm_event::log( + context, + arguments[0].into_int_value(), + arguments[1].into_int_value(), + vec![], + ) + .map(|_| None) + } + Name::Log1 => { + let arguments = self.pop_arguments_llvm::(context)?; + era_compiler_llvm_context::eravm_evm_event::log( + context, + arguments[0].into_int_value(), + arguments[1].into_int_value(), + arguments[2..] + .iter() + .map(|argument| argument.into_int_value()) + .collect(), + ) + .map(|_| None) + } + Name::Log2 => { + let arguments = self.pop_arguments_llvm::(context)?; + era_compiler_llvm_context::eravm_evm_event::log( + context, + arguments[0].into_int_value(), + arguments[1].into_int_value(), + arguments[2..] + .iter() + .map(|argument| argument.into_int_value()) + .collect(), + ) + .map(|_| None) + } + Name::Log3 => { + let arguments = self.pop_arguments_llvm::(context)?; + era_compiler_llvm_context::eravm_evm_event::log( + context, + arguments[0].into_int_value(), + arguments[1].into_int_value(), + arguments[2..] + .iter() + .map(|argument| argument.into_int_value()) + .collect(), + ) + .map(|_| None) + } + Name::Log4 => { + let arguments = self.pop_arguments_llvm::(context)?; + era_compiler_llvm_context::eravm_evm_event::log( + context, + arguments[0].into_int_value(), + arguments[1].into_int_value(), + arguments[2..] + .iter() + .map(|argument| argument.into_int_value()) + .collect(), + ) + .map(|_| None) + } + + Name::Call => { + let arguments = self.pop_arguments::(context)?; + + let gas = arguments[0].value.into_int_value(); + let address = arguments[1].value.into_int_value(); + let value = arguments[2].value.into_int_value(); + let input_offset = arguments[3].value.into_int_value(); + let input_size = arguments[4].value.into_int_value(); + let output_offset = arguments[5].value.into_int_value(); + let output_size = arguments[6].value.into_int_value(); + + let simulation_address: Vec> = arguments + .into_iter() + .map(|mut argument| argument.constant.take()) + .collect(); + + todo!() + /* + era_compiler_llvm_context::eravm_evm_call::default( + context, + context.llvm_runtime().far_call, + gas, + address, + Some(value), + input_offset, + input_size, + output_offset, + output_size, + simulation_address, + ) + .map(Some) + */ + } + Name::StaticCall => { + let arguments = self.pop_arguments::(context)?; + + let gas = arguments[0].value.into_int_value(); + let address = arguments[1].value.into_int_value(); + let input_offset = arguments[2].value.into_int_value(); + let input_size = arguments[3].value.into_int_value(); + let output_offset = arguments[4].value.into_int_value(); + let output_size = arguments[5].value.into_int_value(); + + let simulation_address: Vec> = arguments + .into_iter() + .map(|mut argument| argument.constant.take()) + .collect(); + + era_compiler_llvm_context::eravm_evm_call::default( + context, + context.llvm_runtime().static_call, + gas, + address, + None, + input_offset, + input_size, + output_offset, + output_size, + simulation_address, + ) + .map(Some) + } + Name::DelegateCall => { + let arguments = self.pop_arguments::(context)?; + + let gas = arguments[0].value.into_int_value(); + let address = arguments[1].value.into_int_value(); + let input_offset = arguments[2].value.into_int_value(); + let input_size = arguments[3].value.into_int_value(); + let output_offset = arguments[4].value.into_int_value(); + let output_size = arguments[5].value.into_int_value(); + + let simulation_address: Vec> = arguments + .into_iter() + .map(|mut argument| argument.constant.take()) + .collect(); + + era_compiler_llvm_context::eravm_evm_call::default( + context, + context.llvm_runtime().delegate_call, + gas, + address, + None, + input_offset, + input_size, + output_offset, + output_size, + simulation_address, + ) + .map(Some) + } + + Name::Create | Name::ZkCreate => { + let arguments = self.pop_arguments_llvm::(context)?; + + let value = arguments[0].into_int_value(); + let input_offset = arguments[1].into_int_value(); + let input_length = arguments[2].into_int_value(); + + era_compiler_llvm_context::eravm_evm_create::create( + context, + value, + input_offset, + input_length, + ) + .map(Some) + } + Name::Create2 | Name::ZkCreate2 => { + let arguments = self.pop_arguments_llvm::(context)?; + + let value = arguments[0].into_int_value(); + let input_offset = arguments[1].into_int_value(); + let input_length = arguments[2].into_int_value(); + let salt = arguments[3].into_int_value(); + + era_compiler_llvm_context::eravm_evm_create::create2( + context, + value, + input_offset, + input_length, + Some(salt), + ) + .map(Some) + } + Name::DataOffset => { + let mut arguments = self.pop_arguments::(context)?; + + let identifier = arguments[0].original.take().ok_or_else(|| { + anyhow::anyhow!("{} `dataoffset` object identifier is missing", location) + })?; + + era_compiler_llvm_context::eravm_evm_create::contract_hash(context, identifier) + .map(|argument| Some(argument.value)) + } + Name::DataSize => { + let mut arguments = self.pop_arguments::(context)?; + + let identifier = arguments[0].original.take().ok_or_else(|| { + anyhow::anyhow!("{} `dataoffset` object identifier is missing", location) + })?; + + era_compiler_llvm_context::eravm_evm_create::header_size(context, identifier) + .map(|argument| Some(argument.value)) + } + Name::DataCopy => { + let arguments = self.pop_arguments_llvm::(context)?; + let offset = context.builder().build_int_add( + arguments[0].into_int_value(), + context.field_const( + (era_compiler_common::BYTE_LENGTH_X32 + + era_compiler_common::BYTE_LENGTH_FIELD) + as u64, + ), + "datacopy_contract_hash_offset", + )?; + era_compiler_llvm_context::eravm_evm_memory::store( + context, + offset, + arguments[1].into_int_value(), + ) + .map(|_| None) + } + + Name::LinkerSymbol => { + let mut arguments = self.pop_arguments::(context)?; + let path = arguments[0].original.take().ok_or_else(|| { + anyhow::anyhow!("{} Linker symbol literal is missing", location) + })?; + + Ok(Some( + context + .resolve_library(path.as_str())? + .as_basic_value_enum(), + )) + } + Name::MemoryGuard => { + let arguments = self.pop_arguments_llvm::(context)?; + Ok(Some(arguments[0])) + } + + Name::Address | Name::Caller => { + Ok(Some(context.integer_const(256, 0).as_basic_value_enum())) + } + + Name::CallValue => { + era_compiler_llvm_context::eravm_evm_ether_gas::value(context).map(Some) + } + Name::Gas => era_compiler_llvm_context::eravm_evm_ether_gas::gas(context).map(Some), + Name::Balance => { + let arguments = self.pop_arguments_llvm::(context)?; + + let address = arguments[0].into_int_value(); + era_compiler_llvm_context::eravm_evm_ether_gas::balance(context, address).map(Some) + } + Name::SelfBalance => todo!(), + + Name::GasLimit => { + era_compiler_llvm_context::eravm_evm_contract_context::gas_limit(context).map(Some) + } + Name::GasPrice => { + era_compiler_llvm_context::eravm_evm_contract_context::gas_price(context).map(Some) + } + Name::Origin => { + era_compiler_llvm_context::eravm_evm_contract_context::origin(context).map(Some) + } + Name::ChainId => { + era_compiler_llvm_context::eravm_evm_contract_context::chain_id(context).map(Some) + } + Name::Timestamp => { + era_compiler_llvm_context::eravm_evm_contract_context::block_timestamp(context) + .map(Some) + } + Name::Number => { + era_compiler_llvm_context::eravm_evm_contract_context::block_number(context) + .map(Some) + } + Name::BlockHash => { + let arguments = self.pop_arguments_llvm::(context)?; + let index = arguments[0].into_int_value(); + + era_compiler_llvm_context::eravm_evm_contract_context::block_hash(context, index) + .map(Some) + } + Name::BlobHash => { + let _arguments = self.pop_arguments_llvm::(context)?; + anyhow::bail!( + "{} The `BLOBHASH` instruction is not supported until zkVM v1.5.0", + location + ); + } + Name::Difficulty | Name::Prevrandao => { + era_compiler_llvm_context::eravm_evm_contract_context::difficulty(context).map(Some) + } + Name::CoinBase => { + era_compiler_llvm_context::eravm_evm_contract_context::coinbase(context).map(Some) + } + Name::BaseFee => { + era_compiler_llvm_context::eravm_evm_contract_context::basefee(context).map(Some) + } + Name::BlobBaseFee => { + anyhow::bail!( + "{} The `BLOBBASEFEE` instruction is not supported until zkVM v1.5.0", + location + ); + } + Name::MSize => { + era_compiler_llvm_context::eravm_evm_contract_context::msize(context).map(Some) + } + + Name::Verbatim { + input_size, + output_size, + } => verbatim::verbatim(context, &mut self, input_size, output_size), + + Name::CallCode => { + let _arguments = self.pop_arguments_llvm::(context)?; + anyhow::bail!("{} The `CALLCODE` instruction is not supported", location) + } + Name::Pc => anyhow::bail!("{} The `PC` instruction is not supported", location), + Name::ExtCodeCopy => { + let _arguments = self.pop_arguments_llvm::(context)?; + anyhow::bail!( + "{} The `EXTCODECOPY` instruction is not supported", + location + ) + } + Name::SelfDestruct => { + let _arguments = self.pop_arguments_llvm::(context)?; + anyhow::bail!( + "{} The `SELFDESTRUCT` instruction is not supported", + location + ) + } + + Name::ZkToL1 => { + let [is_first, in_0, in_1] = self.pop_arguments_llvm::(context)?; + + era_compiler_llvm_context::eravm_general::to_l1( + context, + is_first.into_int_value(), + in_0.into_int_value(), + in_1.into_int_value(), + ) + .map(Some) + } + Name::ZkCodeSource => { + era_compiler_llvm_context::eravm_general::code_source(context).map(Some) + } + Name::ZkPrecompile => { + let [in_0, in_1] = self.pop_arguments_llvm::(context)?; + + era_compiler_llvm_context::eravm_general::precompile( + context, + in_0.into_int_value(), + in_1.into_int_value(), + ) + .map(Some) + } + Name::ZkMeta => era_compiler_llvm_context::eravm_general::meta(context).map(Some), + Name::ZkSetContextU128 => { + let [value] = self.pop_arguments_llvm::(context)?; + + era_compiler_llvm_context::eravm_general::set_context_value( + context, + value.into_int_value(), + ) + .map(|_| None) + } + Name::ZkSetPubdataPrice => { + let [value] = self.pop_arguments_llvm::(context)?; + + era_compiler_llvm_context::eravm_general::set_pubdata_price( + context, + value.into_int_value(), + ) + .map(|_| None) + } + Name::ZkIncrementTxCounter => { + era_compiler_llvm_context::eravm_general::increment_tx_counter(context) + .map(|_| None) + } + Name::ZkEventInitialize => { + let [operand_1, operand_2] = self.pop_arguments_llvm::(context)?; + + era_compiler_llvm_context::eravm_general::event( + context, + operand_1.into_int_value(), + operand_2.into_int_value(), + true, + ) + .map(|_| None) + } + Name::ZkEventWrite => { + let [operand_1, operand_2] = self.pop_arguments_llvm::(context)?; + + era_compiler_llvm_context::eravm_general::event( + context, + operand_1.into_int_value(), + operand_2.into_int_value(), + false, + ) + .map(|_| None) + } + + Name::ZkMimicCall => { + let [address, abi_data, mimic] = self.pop_arguments_llvm::(context)?; + + era_compiler_llvm_context::eravm_call::mimic( + context, + context.llvm_runtime().mimic_call, + address.into_int_value(), + mimic.into_int_value(), + abi_data.as_basic_value_enum(), + vec![], + ) + .map(Some) + } + Name::ZkSystemMimicCall => { + let [address, abi_data, mimic, extra_value_1, extra_value_2] = + self.pop_arguments_llvm::(context)?; + + era_compiler_llvm_context::eravm_call::mimic( + context, + context.llvm_runtime().mimic_call, + address.into_int_value(), + mimic.into_int_value(), + abi_data.as_basic_value_enum(), + vec![ + extra_value_1.into_int_value(), + extra_value_2.into_int_value(), + ], + ) + .map(Some) + } + Name::ZkMimicCallByRef => { + let [address, mimic] = self.pop_arguments_llvm::(context)?; + let abi_data = context.get_global_value( + era_compiler_llvm_context::eravm_const::GLOBAL_ACTIVE_POINTER, + )?; + + era_compiler_llvm_context::eravm_call::mimic( + context, + context.llvm_runtime().mimic_call_byref, + address.into_int_value(), + mimic.into_int_value(), + abi_data, + vec![], + ) + .map(Some) + } + Name::ZkSystemMimicCallByRef => { + let [address, mimic, extra_value_1, extra_value_2] = + self.pop_arguments_llvm::(context)?; + let abi_data = context.get_global_value( + era_compiler_llvm_context::eravm_const::GLOBAL_ACTIVE_POINTER, + )?; + + era_compiler_llvm_context::eravm_call::mimic( + context, + context.llvm_runtime().mimic_call_byref, + address.into_int_value(), + mimic.into_int_value(), + abi_data, + vec![ + extra_value_1.into_int_value(), + extra_value_2.into_int_value(), + ], + ) + .map(Some) + } + Name::ZkRawCall => { + unimplemented!() + } + Name::ZkRawCallByRef => { + let [address, output_offset, output_length] = + self.pop_arguments_llvm::(context)?; + let abi_data = context.get_global_value( + era_compiler_llvm_context::eravm_const::GLOBAL_ACTIVE_POINTER, + )?; + + era_compiler_llvm_context::eravm_call::raw_far( + context, + context.llvm_runtime().far_call_byref, + address.into_int_value(), + abi_data, + output_offset.into_int_value(), + output_length.into_int_value(), + ) + .map(Some) + } + Name::ZkSystemCall => { + unimplemented!() + } + Name::ZkSystemCallByRef => { + let [address, extra_value_1, extra_value_2, extra_value_3, extra_value_4] = + self.pop_arguments_llvm::(context)?; + let abi_data = context.get_global_value( + era_compiler_llvm_context::eravm_const::GLOBAL_ACTIVE_POINTER, + )?; + + era_compiler_llvm_context::eravm_call::system( + context, + context.llvm_runtime().far_call_byref, + address.into_int_value(), + abi_data, + context.field_const(0), + context.field_const(0), + vec![ + extra_value_1.into_int_value(), + extra_value_2.into_int_value(), + extra_value_3.into_int_value(), + extra_value_4.into_int_value(), + ], + ) + .map(Some) + } + Name::ZkStaticRawCall => { + let [address, abi_data, output_offset, output_length] = + self.pop_arguments_llvm::(context)?; + + era_compiler_llvm_context::eravm_call::raw_far( + context, + context.llvm_runtime().static_call, + address.into_int_value(), + abi_data.as_basic_value_enum(), + output_offset.into_int_value(), + output_length.into_int_value(), + ) + .map(Some) + } + Name::ZkStaticRawCallByRef => { + let [address, output_offset, output_length] = + self.pop_arguments_llvm::(context)?; + let abi_data = context.get_global_value( + era_compiler_llvm_context::eravm_const::GLOBAL_ACTIVE_POINTER, + )?; + + era_compiler_llvm_context::eravm_call::raw_far( + context, + context.llvm_runtime().static_call_byref, + address.into_int_value(), + abi_data, + output_offset.into_int_value(), + output_length.into_int_value(), + ) + .map(Some) + } + Name::ZkStaticSystemCall => { + let [address, abi_data, extra_value_1, extra_value_2, extra_value_3, extra_value_4] = + self.pop_arguments_llvm::(context)?; + + era_compiler_llvm_context::eravm_call::system( + context, + context.llvm_runtime().static_call, + address.into_int_value(), + abi_data, + context.field_const(0), + context.field_const(0), + vec![ + extra_value_1.into_int_value(), + extra_value_2.into_int_value(), + extra_value_3.into_int_value(), + extra_value_4.into_int_value(), + ], + ) + .map(Some) + } + Name::ZkStaticSystemCallByRef => { + let [address, extra_value_1, extra_value_2, extra_value_3, extra_value_4] = + self.pop_arguments_llvm::(context)?; + let abi_data = context.get_global_value( + era_compiler_llvm_context::eravm_const::GLOBAL_ACTIVE_POINTER, + )?; + + era_compiler_llvm_context::eravm_call::system( + context, + context.llvm_runtime().static_call_byref, + address.into_int_value(), + abi_data, + context.field_const(0), + context.field_const(0), + vec![ + extra_value_1.into_int_value(), + extra_value_2.into_int_value(), + extra_value_3.into_int_value(), + extra_value_4.into_int_value(), + ], + ) + .map(Some) + } + Name::ZkDelegateRawCall => { + let [address, abi_data, output_offset, output_length] = + self.pop_arguments_llvm::(context)?; + + era_compiler_llvm_context::eravm_call::raw_far( + context, + context.llvm_runtime().delegate_call, + address.into_int_value(), + abi_data.as_basic_value_enum(), + output_offset.into_int_value(), + output_length.into_int_value(), + ) + .map(Some) + } + Name::ZkDelegateRawCallByRef => { + let [address, output_offset, output_length] = + self.pop_arguments_llvm::(context)?; + let abi_data = context.get_global_value( + era_compiler_llvm_context::eravm_const::GLOBAL_ACTIVE_POINTER, + )?; + + era_compiler_llvm_context::eravm_call::raw_far( + context, + context.llvm_runtime().delegate_call_byref, + address.into_int_value(), + abi_data, + output_offset.into_int_value(), + output_length.into_int_value(), + ) + .map(Some) + } + Name::ZkDelegateSystemCall => { + let [address, abi_data, extra_value_1, extra_value_2, extra_value_3, extra_value_4] = + self.pop_arguments_llvm::(context)?; + + era_compiler_llvm_context::eravm_call::system( + context, + context.llvm_runtime().delegate_call, + address.into_int_value(), + abi_data, + context.field_const(0), + context.field_const(0), + vec![ + extra_value_1.into_int_value(), + extra_value_2.into_int_value(), + extra_value_3.into_int_value(), + extra_value_4.into_int_value(), + ], + ) + .map(Some) + } + Name::ZkDelegateSystemCallByRef => { + let [address, extra_value_1, extra_value_2, extra_value_3, extra_value_4] = + self.pop_arguments_llvm::(context)?; + let abi_data = context.get_global_value( + era_compiler_llvm_context::eravm_const::GLOBAL_ACTIVE_POINTER, + )?; + + era_compiler_llvm_context::eravm_call::system( + context, + context.llvm_runtime().delegate_call_byref, + address.into_int_value(), + abi_data, + context.field_const(0), + context.field_const(0), + vec![ + extra_value_1.into_int_value(), + extra_value_2.into_int_value(), + extra_value_3.into_int_value(), + extra_value_4.into_int_value(), + ], + ) + .map(Some) + } + + Name::ZkLoadCalldataIntoActivePtr => { + era_compiler_llvm_context::eravm_abi::calldata_ptr_to_active(context).map(|_| None) + } + Name::ZkLoadReturndataIntoActivePtr => { + era_compiler_llvm_context::eravm_abi::return_data_ptr_to_active(context) + .map(|_| None) + } + Name::ZkPtrAddIntoActive => { + let [offset] = self.pop_arguments_llvm::(context)?; + + era_compiler_llvm_context::eravm_abi::active_ptr_add_assign( + context, + offset.into_int_value(), + ) + .map(|_| None) + } + Name::ZkPtrShrinkIntoActive => { + let [offset] = self.pop_arguments_llvm::(context)?; + + era_compiler_llvm_context::eravm_abi::active_ptr_shrink_assign( + context, + offset.into_int_value(), + ) + .map(|_| None) + } + Name::ZkPtrPackIntoActive => { + let [data] = self.pop_arguments_llvm::(context)?; + + era_compiler_llvm_context::eravm_abi::active_ptr_pack_assign( + context, + data.into_int_value(), + ) + .map(|_| None) + } + + Name::ZkMultiplicationHigh => { + let [operand_1, operand_2] = self.pop_arguments_llvm::(context)?; + + era_compiler_llvm_context::eravm_math::multiplication_512( + context, + operand_1.into_int_value(), + operand_2.into_int_value(), + ) + .map(Some) + } + + Name::ZkGlobalLoad => { + let [mut key] = self.pop_arguments::(context)?; + let key = key.original.take().ok_or_else(|| { + anyhow::anyhow!("{} `$zk_global_load` literal is missing", location) + })?; + + context.get_global_value(key.as_str()).map(Some) + } + Name::ZkGlobalExtraAbiData => { + let [index] = self.pop_arguments_llvm::(context)?; + + era_compiler_llvm_context::eravm_abi::get_extra_abi_data( + context, + index.into_int_value(), + ) + .map(Some) + } + Name::ZkGlobalStore => { + let [mut key, value] = self.pop_arguments::(context)?; + let key = key.original.take().ok_or_else(|| { + anyhow::anyhow!("{} `$zk_global_store` literal is missing", location) + })?; + let value = value.value.into_int_value(); + + context.set_global( + key.as_str(), + context.field_type(), + era_compiler_llvm_context::EraVMAddressSpace::Stack, + value, + ); + Ok(None) + } + } + } + + /// + /// Pops the specified number of arguments, converted into their LLVM values. + /// + fn pop_arguments_llvm<'ctx, D, const N: usize>( + &mut self, + context: &mut era_compiler_llvm_context::EraVMContext<'ctx, D>, + ) -> anyhow::Result<[inkwell::values::BasicValueEnum<'ctx>; N]> + where + D: era_compiler_llvm_context::EraVMDependency + Clone, + { + let mut arguments = Vec::with_capacity(N); + for expression in self.arguments.drain(0..N).rev() { + arguments.push(expression.into_llvm(context)?.expect("Always exists").value); + } + arguments.reverse(); + + Ok(arguments.try_into().expect("Always successful")) + } + + /// + /// Pops the specified number of arguments. + /// + fn pop_arguments<'ctx, D, const N: usize>( + &mut self, + context: &mut era_compiler_llvm_context::EraVMContext<'ctx, D>, + ) -> anyhow::Result<[era_compiler_llvm_context::EraVMArgument<'ctx>; N]> + where + D: era_compiler_llvm_context::EraVMDependency + Clone, + { + let mut arguments = Vec::with_capacity(N); + for expression in self.arguments.drain(0..N).rev() { + arguments.push(expression.into_llvm(context)?.expect("Always exists")); + } + arguments.reverse(); + + Ok(arguments.try_into().expect("Always successful")) + } +} diff --git a/crates/solidity/src/yul/parser/statement/expression/function_call/name.rs b/crates/solidity/src/yul/parser/statement/expression/function_call/name.rs new file mode 100644 index 0000000..c2af1ee --- /dev/null +++ b/crates/solidity/src/yul/parser/statement/expression/function_call/name.rs @@ -0,0 +1,491 @@ +//! +//! The function name. +//! + +use serde::Deserialize; +use serde::Serialize; + +/// +/// The function name. +/// +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +pub enum Name { + /// The user-defined function. + UserDefined(String), + + /// `x + y` + Add, + /// `x - y` + Sub, + /// `x * y` + Mul, + /// `x / y` or `0` if `y == 0` + Div, + /// `x % y` or `0` if `y == 0` + Mod, + /// `x / y`, for signed numbers in two’s complement, `0` if `y == 0` + Sdiv, + /// `x % y`, for signed numbers in two’s complement, `0` if `y == 0` + Smod, + + /// `1` if `x < y`, `0` otherwise + Lt, + /// `1` if `x > y`, `0` otherwise + Gt, + /// `1` if `x == y`, `0` otherwise + Eq, + /// `1` if `x == 0`, `0` otherwise + IsZero, + /// `1` if `x < y`, `0` otherwise, for signed numbers in two’s complement + Slt, + /// `1` if `x > y`, `0` otherwise, for signed numbers in two’s complement + Sgt, + + /// bitwise "or" of `x` and `y` + Or, + /// bitwise "xor" of `x` and `y` + Xor, + /// bitwise "not" of `x` (every bit of `x` is negated) + Not, + /// bitwise "and" of `x` and `y` + And, + /// logical shift left `y` by `x` bits + Shl, + /// logical shift right `y` by `x` bits + Shr, + /// signed arithmetic shift right `y` by `x` bits + Sar, + /// `n`th byte of `x`, where the most significant byte is the `0`th byte + Byte, + /// discard value x + Pop, + + /// `(x + y) % m` with arbitrary precision arithmetic, `0` if `m == 0` + AddMod, + /// `(x * y) % m` with arbitrary precision arithmetic, `0` if `m == 0` + MulMod, + /// `x` to the power of `y` + Exp, + /// sign extend from `(i*8+7)`th bit counting from least significant + SignExtend, + + /// `keccak(mem[p…(p+n)))` + Keccak256, + + /// `mem[p…(p+32))` + MLoad, + /// `mem[p…(p+32)) := v` + MStore, + /// `mem[p] := v & 0xff` (only modifies a single byte) + MStore8, + /// heap memory copy + MCopy, + + /// `storage[p]` + SLoad, + /// `storage[p] := v` + SStore, + /// transient `storage[p]` + TLoad, + /// transient `storage[p] := v` + TStore, + /// `loadimmutable` storage read + LoadImmutable, + /// `setimmutable` storage write + SetImmutable, + + /// call data starting from position `p` (32 bytes) + CallDataLoad, + /// size of call data in bytes + CallDataSize, + /// copy `s` bytes from calldata at position `f` to memory at position `t` + CallDataCopy, + /// size of the code of the current contract / execution context + CodeSize, + /// copy `s` bytes from code at position `f` to mem at position `t` + CodeCopy, + /// size of the code at address `a` + ExtCodeSize, + /// code hash of address `a` + ExtCodeHash, + /// size of the last returndata + ReturnDataSize, + /// copy `s` bytes from returndata at position `f` to mem at position `t` + ReturnDataCopy, + + /// end execution, return data `mem[p…(p+s))` + Return, + /// end execution, revert state changes, return data `mem[p…(p+s))` + Revert, + /// stop execution, identical to `return(0, 0)` + Stop, + /// end execution with invalid instruction + Invalid, + + /// log without topics and data `mem[p…(p+s))` + Log0, + /// log with topic t1 and data `mem[p…(p+s))` + Log1, + /// log with topics t1, t2 and data `mem[p…(p+s))` + Log2, + /// log with topics t1, t2, t3 and data `mem[p…(p+s))` + Log3, + /// log with topics t1, t2, t3, t4 and data `mem[p…(p+s))` + Log4, + + /// call contract at address a with input `mem[in…(in+insize))` providing `g` gas and `v` wei + /// and output area `mem[out…(out+outsize))` returning 0 on error (e.g. out of gas) + /// and 1 on success + /// [See more](https://docs.soliditylang.org/en/v0.8.2/yul.html#yul-call-return-area) + Call, + /// identical to call but only use the code from a and stay in the context of the current + /// contract otherwise + CallCode, + /// identical to `callcode` but also keeps `caller` and `callvalue` + DelegateCall, + /// identical to `call(g, a, 0, in, insize, out, outsize)` but do not allows state modifications + StaticCall, + + /// create new contract with code `mem[p…(p+n))` and send `v` wei and return the new address + /// + /// Passes bytecode to the system contracts. + Create, + /// create new contract with code `mem[p…(p+n))` at address + /// `keccak256(0xff . this . s . keccak256(mem[p…(p+n)))` and send `v` wei and return the + /// new address, where `0xff` is a 1-byte value, this is the current contract’s address as a + /// 20-byte value and `s` is a big-endian 256-bit value + /// + /// Passes bytecode to the system contracts. + Create2, + /// create new contract with code `mem[p…(p+n))` and send `v` wei and return the new address + /// + /// Passes hash to the system contracts. + ZkCreate, + /// create new contract with code `mem[p…(p+n))` at address + /// `keccak256(0xff . this . s . keccak256(mem[p…(p+n)))` and send `v` wei and return the + /// new address, where `0xff` is a 1-byte value, this is the current contract’s address as a + /// 20-byte value and `s` is a big-endian 256-bit value + /// + /// Passes hash to the system contracts. + ZkCreate2, + /// returns the size in the data area + DataSize, + /// is equivalent to `CodeCopy` + DataCopy, + /// returns the offset in the data area + DataOffset, + + /// `linkersymbol` is a stub call + LinkerSymbol, + /// `memoryguard` is a stub call + MemoryGuard, + + /// address of the current contract / execution context + Address, + /// call sender (excluding `delegatecall`) + Caller, + + /// wei sent together with the current call + CallValue, + /// gas still available to execution + Gas, + /// wei balance at address `a` + Balance, + /// equivalent to `balance(address())`, but cheaper + SelfBalance, + + /// block gas limit of the current block + GasLimit, + /// gas price of the transaction + GasPrice, + /// transaction sender + Origin, + /// ID of the executing chain (EIP 1344) + ChainId, + /// current block number + Number, + /// timestamp of the current block in seconds since the epoch + Timestamp, + /// hash of block nr b - only for last 256 blocks excluding current + BlockHash, + /// versioned hash of transaction’s i-th blob + BlobHash, + /// difficulty of the current block + Difficulty, + /// https://eips.ethereum.org/EIPS/eip-4399 + Prevrandao, + /// current mining beneficiary + CoinBase, + /// size of memory, i.e. largest accessed memory index + MSize, + + /// verbatim instruction with 0 inputs and 0 outputs + /// only works in the Yul mode, so it is mostly used as a tool for extending Yul for zkSync + Verbatim { + /// the number of input arguments + input_size: usize, + /// the number of output arguments + output_size: usize, + }, + + /// current block’s base fee (EIP-3198 and EIP-1559) + BaseFee, + /// current block’s blob base fee (EIP-7516 and EIP-4844) + BlobBaseFee, + /// current position in code + Pc, + /// like `codecopy(t, f, s)` but take code at address `a` + ExtCodeCopy, + /// end execution, destroy current contract and send funds to `a` + SelfDestruct, + + /// The eponymous EraVM Yul extension instruction. + ZkToL1, + /// The eponymous EraVM Yul extension instruction. + ZkCodeSource, + /// The eponymous EraVM Yul extension instruction. + ZkPrecompile, + /// The eponymous EraVM Yul extension instruction. + ZkMeta, + /// The eponymous EraVM Yul extension instruction. + ZkSetContextU128, + /// The eponymous EraVM Yul extension instruction. + ZkSetPubdataPrice, + /// The eponymous EraVM Yul extension instruction. + ZkIncrementTxCounter, + /// The eponymous EraVM Yul extension instruction. + ZkEventInitialize, + /// The eponymous EraVM Yul extension instruction. + ZkEventWrite, + + /// The eponymous EraVM Yul extension instruction. + ZkMimicCall, + /// The eponymous EraVM Yul extension instruction. + ZkSystemMimicCall, + /// The eponymous EraVM Yul extension instruction. + ZkMimicCallByRef, + /// The eponymous EraVM Yul extension instruction. + ZkSystemMimicCallByRef, + /// The eponymous EraVM Yul extension instruction. + ZkRawCall, + /// The eponymous EraVM Yul extension instruction. + ZkRawCallByRef, + /// The eponymous EraVM Yul extension instruction. + ZkSystemCall, + /// The eponymous EraVM Yul extension instruction. + ZkSystemCallByRef, + /// The eponymous EraVM Yul extension instruction. + ZkStaticRawCall, + /// The eponymous EraVM Yul extension instruction. + ZkStaticRawCallByRef, + /// The eponymous EraVM Yul extension instruction. + ZkStaticSystemCall, + /// The eponymous EraVM Yul extension instruction. + ZkStaticSystemCallByRef, + /// The eponymous EraVM Yul extension instruction. + ZkDelegateRawCall, + /// The eponymous EraVM Yul extension instruction. + ZkDelegateRawCallByRef, + /// The eponymous EraVM Yul extension instruction. + ZkDelegateSystemCall, + /// The eponymous EraVM Yul extension instruction. + ZkDelegateSystemCallByRef, + + /// The eponymous EraVM Yul extension instruction. + ZkLoadCalldataIntoActivePtr, + /// The eponymous EraVM Yul extension instruction. + ZkLoadReturndataIntoActivePtr, + /// The eponymous EraVM Yul extension instruction. + ZkPtrAddIntoActive, + /// The eponymous EraVM Yul extension instruction. + ZkPtrShrinkIntoActive, + /// The eponymous EraVM Yul extension instruction. + ZkPtrPackIntoActive, + + /// The eponymous EraVM Yul extension instruction. + ZkMultiplicationHigh, + + /// The eponymous EraVM Yul extension instruction. + ZkGlobalLoad, + /// The eponymous EraVM Yul extension instruction. + ZkGlobalExtraAbiData, + /// The eponymous EraVM Yul extension instruction. + ZkGlobalStore, +} + +impl Name { + /// + /// Tries parsing the verbatim instruction. + /// + fn parse_verbatim(input: &str) -> Option { + let verbatim = input.strip_prefix("verbatim")?; + let regex = regex::Regex::new(r"_(\d+)i_(\d+)o").expect("Always valid"); + let captures = regex.captures(verbatim)?; + let input_size: usize = captures.get(1)?.as_str().parse().ok()?; + let output_size: usize = captures.get(2)?.as_str().parse().ok()?; + Some(Self::Verbatim { + input_size, + output_size, + }) + } +} + +impl From<&str> for Name { + fn from(input: &str) -> Self { + if let Some(verbatim) = Self::parse_verbatim(input) { + return verbatim; + } + + match input { + "add" => Self::Add, + "sub" => Self::Sub, + "mul" => Self::Mul, + "div" => Self::Div, + "mod" => Self::Mod, + "sdiv" => Self::Sdiv, + "smod" => Self::Smod, + + "lt" => Self::Lt, + "gt" => Self::Gt, + "eq" => Self::Eq, + "iszero" => Self::IsZero, + "slt" => Self::Slt, + "sgt" => Self::Sgt, + + "or" => Self::Or, + "xor" => Self::Xor, + "not" => Self::Not, + "and" => Self::And, + "shl" => Self::Shl, + "shr" => Self::Shr, + "sar" => Self::Sar, + "byte" => Self::Byte, + "pop" => Self::Pop, + + "addmod" => Self::AddMod, + "mulmod" => Self::MulMod, + "exp" => Self::Exp, + "signextend" => Self::SignExtend, + + "keccak256" => Self::Keccak256, + + "mload" => Self::MLoad, + "mstore" => Self::MStore, + "mstore8" => Self::MStore8, + "mcopy" => Self::MCopy, + + "sload" => Self::SLoad, + "sstore" => Self::SStore, + "tload" => Self::TLoad, + "tstore" => Self::TStore, + "loadimmutable" => Self::LoadImmutable, + "setimmutable" => Self::SetImmutable, + + "calldataload" => Self::CallDataLoad, + "calldatasize" => Self::CallDataSize, + "calldatacopy" => Self::CallDataCopy, + "codesize" => Self::CodeSize, + "codecopy" => Self::CodeCopy, + "returndatasize" => Self::ReturnDataSize, + "returndatacopy" => Self::ReturnDataCopy, + "extcodesize" => Self::ExtCodeSize, + "extcodehash" => Self::ExtCodeHash, + + "return" => Self::Return, + "revert" => Self::Revert, + + "log0" => Self::Log0, + "log1" => Self::Log1, + "log2" => Self::Log2, + "log3" => Self::Log3, + "log4" => Self::Log4, + + "call" => Self::Call, + "delegatecall" => Self::DelegateCall, + "staticcall" => Self::StaticCall, + + "create" => Self::Create, + "create2" => Self::Create2, + "$zk_create" => Self::ZkCreate, + "$zk_create2" => Self::ZkCreate2, + "datasize" => Self::DataSize, + "dataoffset" => Self::DataOffset, + "datacopy" => Self::DataCopy, + + "stop" => Self::Stop, + "invalid" => Self::Invalid, + + "linkersymbol" => Self::LinkerSymbol, + "memoryguard" => Self::MemoryGuard, + + "address" => Self::Address, + "caller" => Self::Caller, + + "callvalue" => Self::CallValue, + "gas" => Self::Gas, + "balance" => Self::Balance, + "selfbalance" => Self::SelfBalance, + + "gaslimit" => Self::GasLimit, + "gasprice" => Self::GasPrice, + "origin" => Self::Origin, + "chainid" => Self::ChainId, + "timestamp" => Self::Timestamp, + "number" => Self::Number, + "blockhash" => Self::BlockHash, + "blobhash" => Self::BlobHash, + "difficulty" => Self::Difficulty, + "prevrandao" => Self::Prevrandao, + "coinbase" => Self::CoinBase, + "basefee" => Self::BaseFee, + "blobbasefee" => Self::BlobBaseFee, + "msize" => Self::MSize, + + "callcode" => Self::CallCode, + "pc" => Self::Pc, + "extcodecopy" => Self::ExtCodeCopy, + "selfdestruct" => Self::SelfDestruct, + + "$zk_to_l1" => Self::ZkToL1, + "$zk_code_source" => Self::ZkCodeSource, + "$zk_precompile" => Self::ZkPrecompile, + "$zk_meta" => Self::ZkMeta, + "$zk_set_context_u128" => Self::ZkSetContextU128, + "$zk_set_pubdata_price" => Self::ZkSetPubdataPrice, + "$zk_increment_tx_counter" => Self::ZkIncrementTxCounter, + "$zk_event_initialize" => Self::ZkEventInitialize, + "$zk_event_write" => Self::ZkEventWrite, + + "$zk_mimic_call" => Self::ZkMimicCall, + "$zk_system_mimic_call" => Self::ZkSystemMimicCall, + "$zk_mimic_call_byref" => Self::ZkMimicCallByRef, + "$zk_system_mimic_call_byref" => Self::ZkSystemMimicCallByRef, + "$zk_raw_call" => Self::ZkRawCall, + "$zk_raw_call_byref" => Self::ZkRawCallByRef, + "$zk_system_call" => Self::ZkSystemCall, + "$zk_system_call_byref" => Self::ZkSystemCallByRef, + "$zk_static_raw_call" => Self::ZkStaticRawCall, + "$zk_static_raw_call_byref" => Self::ZkStaticRawCallByRef, + "$zk_static_system_call" => Self::ZkStaticSystemCall, + "$zk_static_system_call_byref" => Self::ZkStaticSystemCallByRef, + "$zk_delegate_raw_call" => Self::ZkDelegateRawCall, + "$zk_delegate_raw_call_byref" => Self::ZkDelegateRawCallByRef, + "$zk_delegate_system_call" => Self::ZkDelegateSystemCall, + "$zk_delegate_system_call_byref" => Self::ZkDelegateSystemCallByRef, + + "$zk_load_calldata_into_active_ptr" => Self::ZkLoadCalldataIntoActivePtr, + "$zk_load_returndata_into_active_ptr" => Self::ZkLoadReturndataIntoActivePtr, + "$zk_ptr_add_into_active" => Self::ZkPtrAddIntoActive, + "$zk_ptr_shrink_into_active" => Self::ZkPtrShrinkIntoActive, + "$zk_ptr_pack_into_active" => Self::ZkPtrPackIntoActive, + + "$zk_multiplication_high" => Self::ZkMultiplicationHigh, + + "$zk_global_load" => Self::ZkGlobalLoad, + "$zk_global_extra_abi_data" => Self::ZkGlobalExtraAbiData, + "$zk_global_store" => Self::ZkGlobalStore, + + input => Self::UserDefined(input.to_owned()), + } + } +} diff --git a/crates/solidity/src/yul/parser/statement/expression/function_call/verbatim.rs b/crates/solidity/src/yul/parser/statement/expression/function_call/verbatim.rs new file mode 100644 index 0000000..f8a0c69 --- /dev/null +++ b/crates/solidity/src/yul/parser/statement/expression/function_call/verbatim.rs @@ -0,0 +1,841 @@ +//! +//! Translates the verbatim simulations. +//! + +use anyhow::Ok; + +use crate::yul::parser::statement::expression::function_call::FunctionCall; + +/// +/// Translates the verbatim simulations. +/// +pub fn verbatim<'ctx, D>( + context: &mut era_compiler_llvm_context::EraVMContext<'ctx, D>, + call: &mut FunctionCall, + input_size: usize, + output_size: usize, +) -> anyhow::Result>> +where + D: era_compiler_llvm_context::EraVMDependency + Clone, +{ + if output_size > 1 { + anyhow::bail!( + "{} Verbatim instructions with multiple return values are not supported", + call.location + ); + } + + let mut arguments = call.pop_arguments::(context)?; + let identifier = arguments[0] + .original + .take() + .ok_or_else(|| anyhow::anyhow!("{} Verbatim literal is missing", call.location))?; + match identifier.as_str() { + identifier @ "to_l1" => { + const ARGUMENTS_COUNT: usize = 3; + if input_size != ARGUMENTS_COUNT { + anyhow::bail!( + "{} Internal function `{}` expected {} arguments, found {}", + call.location, + identifier, + ARGUMENTS_COUNT, + input_size + ); + } + + let arguments = call.pop_arguments_llvm::(context)?; + era_compiler_llvm_context::eravm_general::to_l1( + context, + arguments[0].into_int_value(), + arguments[1].into_int_value(), + arguments[2].into_int_value(), + ) + .map(Some) + } + identifier @ "code_source" => { + const ARGUMENTS_COUNT: usize = 0; + if input_size != ARGUMENTS_COUNT { + anyhow::bail!( + "{} Internal function `{}` expected {} arguments, found {}", + call.location, + identifier, + ARGUMENTS_COUNT, + input_size + ); + } + + era_compiler_llvm_context::eravm_general::code_source(context).map(Some) + } + identifier @ "precompile" => { + const ARGUMENTS_COUNT: usize = 2; + if input_size != ARGUMENTS_COUNT { + anyhow::bail!( + "{} Internal function `{}` expected {} arguments, found {}", + call.location, + identifier, + ARGUMENTS_COUNT, + input_size + ); + } + + let arguments = call.pop_arguments_llvm::(context)?; + era_compiler_llvm_context::eravm_general::precompile( + context, + arguments[0].into_int_value(), + arguments[1].into_int_value(), + ) + .map(Some) + } + identifier @ "meta" => { + const ARGUMENTS_COUNT: usize = 0; + if input_size != ARGUMENTS_COUNT { + anyhow::bail!( + "{} Internal function `{}` expected {} arguments, found {}", + call.location, + identifier, + ARGUMENTS_COUNT, + input_size + ); + } + + era_compiler_llvm_context::eravm_general::meta(context).map(Some) + } + identifier @ "mimic_call" => { + const ARGUMENTS_COUNT: usize = 3; + if input_size != ARGUMENTS_COUNT { + anyhow::bail!( + "{} Internal function `{}` expected {} arguments, found {}", + call.location, + identifier, + ARGUMENTS_COUNT, + input_size + ); + } + + let arguments = call.pop_arguments_llvm::(context)?; + era_compiler_llvm_context::eravm_call::mimic( + context, + context.llvm_runtime().mimic_call, + arguments[0].into_int_value(), + arguments[1].into_int_value(), + arguments[2], + vec![context.field_const(0), context.field_const(0)], + ) + .map(Some) + } + identifier @ "mimic_call_byref" => { + const ARGUMENTS_COUNT: usize = 2; + if input_size != ARGUMENTS_COUNT { + anyhow::bail!( + "{} Internal function `{}` expected {} arguments, found {}", + call.location, + identifier, + ARGUMENTS_COUNT, + input_size + ); + } + + let arguments = call.pop_arguments_llvm::(context)?; + era_compiler_llvm_context::eravm_call::mimic( + context, + context.llvm_runtime().mimic_call_byref, + arguments[0].into_int_value(), + arguments[1].into_int_value(), + context.get_global_value( + era_compiler_llvm_context::eravm_const::GLOBAL_ACTIVE_POINTER, + )?, + vec![context.field_const(0), context.field_const(0)], + ) + .map(Some) + } + identifier @ "system_mimic_call" => { + const ARGUMENTS_COUNT: usize = 7; + if input_size != ARGUMENTS_COUNT { + anyhow::bail!( + "{} Internal function `{}` expected {} arguments, found {}", + call.location, + identifier, + ARGUMENTS_COUNT, + input_size + ); + } + + let arguments = call.pop_arguments_llvm::(context)?; + era_compiler_llvm_context::eravm_call::mimic( + context, + context.llvm_runtime().mimic_call, + arguments[0].into_int_value(), + arguments[1].into_int_value(), + arguments[2], + vec![ + arguments[3].into_int_value(), + arguments[4].into_int_value(), + arguments[5].into_int_value(), + arguments[6].into_int_value(), + ], + ) + .map(Some) + } + identifier @ "system_mimic_call_byref" => { + const ARGUMENTS_COUNT: usize = 6; + if input_size != ARGUMENTS_COUNT { + anyhow::bail!( + "{} Internal function `{}` expected {} arguments, found {}", + call.location, + identifier, + ARGUMENTS_COUNT, + input_size + ); + } + + let arguments = call.pop_arguments_llvm::(context)?; + era_compiler_llvm_context::eravm_call::mimic( + context, + context.llvm_runtime().mimic_call_byref, + arguments[0].into_int_value(), + arguments[1].into_int_value(), + context.get_global_value( + era_compiler_llvm_context::eravm_const::GLOBAL_ACTIVE_POINTER, + )?, + vec![ + arguments[2].into_int_value(), + arguments[3].into_int_value(), + arguments[4].into_int_value(), + arguments[5].into_int_value(), + ], + ) + .map(Some) + } + identifier @ "raw_call" => { + const ARGUMENTS_COUNT: usize = 4; + if input_size != ARGUMENTS_COUNT { + anyhow::bail!( + "{} Internal function `{}` expected {} arguments, found {}", + call.location, + identifier, + ARGUMENTS_COUNT, + input_size + ); + } + + let arguments = call.pop_arguments_llvm::(context)?; + todo!() + //era_compiler_llvm_context::eravm_call::raw_far( + // context, + // context.llvm_runtime().far_call, + // arguments[0].into_int_value(), + // arguments[1], + // arguments[2].into_int_value(), + // arguments[3].into_int_value(), + //) + //.map(Some) + } + identifier @ "raw_call_byref" => { + const ARGUMENTS_COUNT: usize = 3; + if input_size != ARGUMENTS_COUNT { + anyhow::bail!( + "{} Internal function `{}` expected {} arguments, found {}", + call.location, + identifier, + ARGUMENTS_COUNT, + input_size + ); + } + + let arguments = call.pop_arguments_llvm::(context)?; + era_compiler_llvm_context::eravm_call::raw_far( + context, + context.llvm_runtime().far_call_byref, + arguments[0].into_int_value(), + context.get_global_value( + era_compiler_llvm_context::eravm_const::GLOBAL_ACTIVE_POINTER, + )?, + arguments[1].into_int_value(), + arguments[2].into_int_value(), + ) + .map(Some) + } + identifier @ "system_call" => { + unimplemented!() + } + identifier @ "system_call_byref" => { + const ARGUMENTS_COUNT: usize = 5; + if input_size != ARGUMENTS_COUNT { + anyhow::bail!( + "{} Internal function `{}` expected {} arguments, found {}", + call.location, + identifier, + ARGUMENTS_COUNT, + input_size + ); + } + + let arguments = call.pop_arguments_llvm::(context)?; + era_compiler_llvm_context::eravm_call::system( + context, + context.llvm_runtime().far_call_byref, + arguments[0].into_int_value(), + context.get_global_value( + era_compiler_llvm_context::eravm_const::GLOBAL_ACTIVE_POINTER, + )?, + context.field_const(0), + context.field_const(0), + vec![ + arguments[1].into_int_value(), + arguments[2].into_int_value(), + arguments[3].into_int_value(), + arguments[4].into_int_value(), + ], + ) + .map(Some) + } + identifier @ "raw_static_call" => { + const ARGUMENTS_COUNT: usize = 4; + if input_size != ARGUMENTS_COUNT { + anyhow::bail!( + "{} Internal function `{}` expected {} arguments, found {}", + call.location, + identifier, + ARGUMENTS_COUNT, + input_size + ); + } + + let arguments = call.pop_arguments_llvm::(context)?; + era_compiler_llvm_context::eravm_call::raw_far( + context, + context.llvm_runtime().static_call, + arguments[0].into_int_value(), + arguments[1], + arguments[2].into_int_value(), + arguments[3].into_int_value(), + ) + .map(Some) + } + identifier @ "raw_static_call_byref" => { + const ARGUMENTS_COUNT: usize = 3; + if input_size != ARGUMENTS_COUNT { + anyhow::bail!( + "{} Internal function `{}` expected {} arguments, found {}", + call.location, + identifier, + ARGUMENTS_COUNT, + input_size + ); + } + + let arguments = call.pop_arguments_llvm::(context)?; + era_compiler_llvm_context::eravm_call::raw_far( + context, + context.llvm_runtime().static_call_byref, + arguments[0].into_int_value(), + context.get_global_value( + era_compiler_llvm_context::eravm_const::GLOBAL_ACTIVE_POINTER, + )?, + arguments[1].into_int_value(), + arguments[2].into_int_value(), + ) + .map(Some) + } + identifier @ "system_static_call" => { + const ARGUMENTS_COUNT: usize = 6; + if input_size != ARGUMENTS_COUNT { + anyhow::bail!( + "{} Internal function `{}` expected {} arguments, found {}", + call.location, + identifier, + ARGUMENTS_COUNT, + input_size + ); + } + + let arguments = call.pop_arguments_llvm::(context)?; + era_compiler_llvm_context::eravm_call::system( + context, + context.llvm_runtime().static_call, + arguments[0].into_int_value(), + arguments[1], + arguments[4].into_int_value(), + arguments[5].into_int_value(), + vec![arguments[2].into_int_value(), arguments[3].into_int_value()], + ) + .map(Some) + } + identifier @ "system_static_call_byref" => { + const ARGUMENTS_COUNT: usize = 5; + if input_size != ARGUMENTS_COUNT { + anyhow::bail!( + "{} Internal function `{}` expected {} arguments, found {}", + call.location, + identifier, + ARGUMENTS_COUNT, + input_size + ); + } + + let arguments = call.pop_arguments_llvm::(context)?; + era_compiler_llvm_context::eravm_call::system( + context, + context.llvm_runtime().static_call_byref, + arguments[0].into_int_value(), + context.get_global_value( + era_compiler_llvm_context::eravm_const::GLOBAL_ACTIVE_POINTER, + )?, + arguments[3].into_int_value(), + arguments[4].into_int_value(), + vec![arguments[1].into_int_value(), arguments[2].into_int_value()], + ) + .map(Some) + } + identifier @ "raw_delegate_call" => { + const ARGUMENTS_COUNT: usize = 4; + if input_size != ARGUMENTS_COUNT { + anyhow::bail!( + "{} Internal function `{}` expected {} arguments, found {}", + call.location, + identifier, + ARGUMENTS_COUNT, + input_size + ); + } + + let arguments = call.pop_arguments_llvm::(context)?; + era_compiler_llvm_context::eravm_call::raw_far( + context, + context.llvm_runtime().delegate_call, + arguments[0].into_int_value(), + arguments[1], + arguments[2].into_int_value(), + arguments[3].into_int_value(), + ) + .map(Some) + } + identifier @ "raw_delegate_call_byref" => { + const ARGUMENTS_COUNT: usize = 3; + if input_size != ARGUMENTS_COUNT { + anyhow::bail!( + "{} Internal function `{}` expected {} arguments, found {}", + call.location, + identifier, + ARGUMENTS_COUNT, + input_size + ); + } + + let arguments = call.pop_arguments_llvm::(context)?; + era_compiler_llvm_context::eravm_call::raw_far( + context, + context.llvm_runtime().delegate_call_byref, + arguments[0].into_int_value(), + context.get_global_value( + era_compiler_llvm_context::eravm_const::GLOBAL_ACTIVE_POINTER, + )?, + arguments[1].into_int_value(), + arguments[2].into_int_value(), + ) + .map(Some) + } + identifier @ "system_delegate_call" => { + const ARGUMENTS_COUNT: usize = 6; + if input_size != ARGUMENTS_COUNT { + anyhow::bail!( + "{} Internal function `{}` expected {} arguments, found {}", + call.location, + identifier, + ARGUMENTS_COUNT, + input_size + ); + } + + let arguments = call.pop_arguments_llvm::(context)?; + era_compiler_llvm_context::eravm_call::system( + context, + context.llvm_runtime().delegate_call, + arguments[0].into_int_value(), + arguments[1], + arguments[4].into_int_value(), + arguments[5].into_int_value(), + vec![arguments[2].into_int_value(), arguments[3].into_int_value()], + ) + .map(Some) + } + identifier @ "system_delegate_call_byref" => { + const ARGUMENTS_COUNT: usize = 5; + if input_size != ARGUMENTS_COUNT { + anyhow::bail!( + "{} Internal function `{}` expected {} arguments, found {}", + call.location, + identifier, + ARGUMENTS_COUNT, + input_size + ); + } + + let arguments = call.pop_arguments_llvm::(context)?; + era_compiler_llvm_context::eravm_call::system( + context, + context.llvm_runtime().delegate_call_byref, + arguments[0].into_int_value(), + context.get_global_value( + era_compiler_llvm_context::eravm_const::GLOBAL_ACTIVE_POINTER, + )?, + arguments[3].into_int_value(), + arguments[4].into_int_value(), + vec![arguments[1].into_int_value(), arguments[2].into_int_value()], + ) + .map(Some) + } + identifier @ "set_context_u128" => { + const ARGUMENTS_COUNT: usize = 1; + if input_size != ARGUMENTS_COUNT { + anyhow::bail!( + "{} Internal function `{}` expected {} arguments, found {}", + call.location, + identifier, + ARGUMENTS_COUNT, + input_size + ); + } + + let arguments = call.pop_arguments_llvm::(context)?; + era_compiler_llvm_context::eravm_general::set_context_value( + context, + arguments[0].into_int_value(), + ) + .map(Some) + } + identifier @ "set_pubdata_price" => { + const ARGUMENTS_COUNT: usize = 1; + if input_size != ARGUMENTS_COUNT { + anyhow::bail!( + "{} Internal function `{}` expected {} arguments, found {}", + call.location, + identifier, + ARGUMENTS_COUNT, + input_size + ); + } + + let arguments = call.pop_arguments_llvm::(context)?; + era_compiler_llvm_context::eravm_general::set_pubdata_price( + context, + arguments[0].into_int_value(), + ) + .map(Some) + } + identifier @ "increment_tx_counter" => { + const ARGUMENTS_COUNT: usize = 0; + if input_size != ARGUMENTS_COUNT { + anyhow::bail!( + "{} Internal function `{}` expected {} arguments, found {}", + call.location, + identifier, + ARGUMENTS_COUNT, + input_size + ); + } + + era_compiler_llvm_context::eravm_general::increment_tx_counter(context).map(Some) + } + identifier @ "event_initialize" => { + const ARGUMENTS_COUNT: usize = 2; + if input_size != ARGUMENTS_COUNT { + anyhow::bail!( + "{} Internal function `{}` expected {} arguments, found {}", + call.location, + identifier, + ARGUMENTS_COUNT, + input_size + ); + } + + let arguments = call.pop_arguments_llvm::(context)?; + era_compiler_llvm_context::eravm_general::event( + context, + arguments[0].into_int_value(), + arguments[1].into_int_value(), + true, + ) + .map(Some) + } + identifier @ "event_write" => { + const ARGUMENTS_COUNT: usize = 2; + if input_size != ARGUMENTS_COUNT { + anyhow::bail!( + "{} Internal function `{}` expected {} arguments, found {}", + call.location, + identifier, + ARGUMENTS_COUNT, + input_size + ); + } + + let arguments = call.pop_arguments_llvm::(context)?; + era_compiler_llvm_context::eravm_general::event( + context, + arguments[0].into_int_value(), + arguments[1].into_int_value(), + false, + ) + .map(Some) + } + identifier @ "calldata_ptr_to_active" => { + const ARGUMENTS_COUNT: usize = 0; + if input_size != ARGUMENTS_COUNT { + anyhow::bail!( + "{} Internal function `{}` expected {} arguments, found {}", + call.location, + identifier, + ARGUMENTS_COUNT, + input_size + ); + } + + era_compiler_llvm_context::eravm_abi::calldata_ptr_to_active(context).map(Some) + } + identifier @ "return_data_ptr_to_active" => { + const ARGUMENTS_COUNT: usize = 0; + if input_size != ARGUMENTS_COUNT { + anyhow::bail!( + "{} Internal function `{}` expected {} arguments, found {}", + call.location, + identifier, + ARGUMENTS_COUNT, + input_size + ); + } + + era_compiler_llvm_context::eravm_abi::return_data_ptr_to_active(context).map(Some) + } + identifier @ "active_ptr_add_assign" => { + const ARGUMENTS_COUNT: usize = 1; + if input_size != ARGUMENTS_COUNT { + anyhow::bail!( + "{} Internal function `{}` expected {} arguments, found {}", + call.location, + identifier, + ARGUMENTS_COUNT, + input_size + ); + } + + let arguments = call.pop_arguments_llvm::(context)?; + era_compiler_llvm_context::eravm_abi::active_ptr_add_assign( + context, + arguments[0].into_int_value(), + ) + .map(Some) + } + identifier @ "active_ptr_shrink_assign" => { + const ARGUMENTS_COUNT: usize = 1; + if input_size != ARGUMENTS_COUNT { + anyhow::bail!( + "{} Internal function `{}` expected {} arguments, found {}", + call.location, + identifier, + ARGUMENTS_COUNT, + input_size + ); + } + + let arguments = call.pop_arguments_llvm::(context)?; + era_compiler_llvm_context::eravm_abi::active_ptr_shrink_assign( + context, + arguments[0].into_int_value(), + ) + .map(Some) + } + identifier @ "active_ptr_pack_assign" => { + const ARGUMENTS_COUNT: usize = 1; + if input_size != ARGUMENTS_COUNT { + anyhow::bail!( + "{} Internal function `{}` expected {} arguments, found {}", + call.location, + identifier, + ARGUMENTS_COUNT, + input_size + ); + } + + let arguments = call.pop_arguments_llvm::(context)?; + era_compiler_llvm_context::eravm_abi::active_ptr_pack_assign( + context, + arguments[0].into_int_value(), + ) + .map(Some) + } + identifier @ "mul_high" => { + const ARGUMENTS_COUNT: usize = 2; + if input_size != ARGUMENTS_COUNT { + anyhow::bail!( + "{} Internal function `{}` expected {} arguments, found {}", + call.location, + identifier, + ARGUMENTS_COUNT, + input_size + ); + } + + let arguments = call.pop_arguments_llvm::(context)?; + era_compiler_llvm_context::eravm_math::multiplication_512( + context, + arguments[0].into_int_value(), + arguments[1].into_int_value(), + ) + .map(Some) + } + identifier @ "throw" => { + const ARGUMENTS_COUNT: usize = 0; + if input_size != ARGUMENTS_COUNT { + anyhow::bail!( + "{} Internal function `{}` expected {} arguments, found {}", + call.location, + identifier, + ARGUMENTS_COUNT, + input_size + ); + } + + era_compiler_llvm_context::eravm_utils::throw(context); + Ok(None) + } + identifier + if identifier.starts_with( + era_compiler_llvm_context::eravm_const::GLOBAL_VERBATIM_GETTER_PREFIX, + ) => + { + const ARGUMENTS_COUNT: usize = 0; + if input_size != ARGUMENTS_COUNT { + anyhow::bail!( + "{} Internal function `{}` expected {} arguments, found {}", + call.location, + identifier, + ARGUMENTS_COUNT, + input_size + ); + } + + match identifier + .strip_prefix(era_compiler_llvm_context::eravm_const::GLOBAL_VERBATIM_GETTER_PREFIX) + { + Some(identifier) + if identifier + == era_compiler_llvm_context::eravm_const::GLOBAL_CALLDATA_POINTER => + { + context.get_global_value(identifier).map(Some) + } + Some(identifier) + if identifier == era_compiler_llvm_context::eravm_const::GLOBAL_CALL_FLAGS => + { + context.get_global_value(identifier).map(Some) + } + Some(identifier) + if identifier + == era_compiler_llvm_context::eravm_const::GLOBAL_RETURN_DATA_POINTER => + { + context.get_global_value(identifier).map(Some) + } + Some(identifier) + if identifier.starts_with( + era_compiler_llvm_context::eravm_const::GLOBAL_EXTRA_ABI_DATA, + ) => + { + let stripped = identifier + .strip_prefix(era_compiler_llvm_context::eravm_const::GLOBAL_EXTRA_ABI_DATA) + .expect("Always exists"); + let stripped = stripped.strip_prefix('_').ok_or_else(|| { + anyhow::anyhow!( + "{} Invalid global variable identifier `{:?}`", + call.location, + identifier + ) + })?; + let index = stripped.parse::().map_err(|error| { + anyhow::anyhow!( + "{} Invalid global variable identifier `{:?}`: {}", + call.location, + identifier, + error, + ) + })?; + if index >= (era_compiler_llvm_context::eravm_const::EXTRA_ABI_DATA_SIZE as u64) + { + anyhow::bail!( + "{} Extra ABI data overflow. Only indexes `0..=9` are allowed", + call.location, + ); + } + era_compiler_llvm_context::eravm_abi::get_extra_abi_data( + context, + context.field_const(index), + ) + .map(Some) + } + identifier => Err(anyhow::anyhow!( + "{} Invalid global variable identifier `{:?}`", + call.location, + identifier + )), + } + } + identifier @ "active_ptr_data_load" => { + const ARGUMENTS_COUNT: usize = 1; + if input_size != ARGUMENTS_COUNT { + anyhow::bail!( + "{} Internal function `{}` expected {} arguments, found {}", + call.location, + identifier, + ARGUMENTS_COUNT, + input_size + ); + } + + let arguments = call.pop_arguments_llvm::(context)?; + era_compiler_llvm_context::eravm_abi::active_ptr_data_load( + context, + arguments[0].into_int_value(), + ) + .map(Some) + } + identifier @ "active_ptr_data_size" => { + const ARGUMENTS_COUNT: usize = 0; + if input_size != ARGUMENTS_COUNT { + anyhow::bail!( + "{} Internal function `{}` expected {} arguments, found {}", + call.location, + identifier, + ARGUMENTS_COUNT, + input_size + ); + } + + era_compiler_llvm_context::eravm_abi::active_ptr_data_size(context).map(Some) + } + identifier @ "active_ptr_data_copy" => { + const ARGUMENTS_COUNT: usize = 3; + if input_size != ARGUMENTS_COUNT { + anyhow::bail!( + "{} Internal function `{}` expected {} arguments, found {}", + call.location, + identifier, + ARGUMENTS_COUNT, + input_size + ); + } + + let arguments = call.pop_arguments_llvm::(context)?; + era_compiler_llvm_context::eravm_abi::active_ptr_data_copy( + context, + arguments[0].into_int_value(), + arguments[1].into_int_value(), + arguments[2].into_int_value(), + ) + .map(|_| None) + } + identifier => anyhow::bail!( + "{} Found unknown internal function `{}`", + call.location, + identifier + ), + } +} diff --git a/crates/solidity/src/yul/parser/statement/expression/literal.rs b/crates/solidity/src/yul/parser/statement/expression/literal.rs new file mode 100644 index 0000000..4bd9d6d --- /dev/null +++ b/crates/solidity/src/yul/parser/statement/expression/literal.rs @@ -0,0 +1,239 @@ +//! +//! The YUL source code literal. +//! + +use inkwell::values::BasicValue; +use num::Num; +use num::One; +use num::Zero; +use serde::Deserialize; +use serde::Serialize; + +use crate::yul::error::Error; +use crate::yul::lexer::token::lexeme::literal::boolean::Boolean as BooleanLiteral; +use crate::yul::lexer::token::lexeme::literal::integer::Integer as IntegerLiteral; +use crate::yul::lexer::token::lexeme::literal::Literal as LexicalLiteral; +use crate::yul::lexer::token::lexeme::symbol::Symbol; +use crate::yul::lexer::token::lexeme::Lexeme; +use crate::yul::lexer::token::location::Location; +use crate::yul::lexer::token::Token; +use crate::yul::lexer::Lexer; +use crate::yul::parser::error::Error as ParserError; +use crate::yul::parser::r#type::Type; + +/// +/// Represents a literal in YUL without differentiating its type. +/// +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +pub struct Literal { + /// The location. + pub location: Location, + /// The lexical literal. + pub inner: LexicalLiteral, + /// The type, if it has been explicitly specified. + pub yul_type: Option, +} + +impl Literal { + /// + /// The element parser. + /// + pub fn parse(lexer: &mut Lexer, initial: Option) -> Result { + let token = crate::yul::parser::take_or_next(initial, lexer)?; + + let (location, literal) = match token { + Token { + lexeme: Lexeme::Literal(literal), + location, + .. + } => (location, literal), + token => { + return Err(ParserError::InvalidToken { + location: token.location, + expected: vec!["{literal}"], + found: token.lexeme.to_string(), + } + .into()); + } + }; + + let yul_type = match lexer.peek()? { + Token { + lexeme: Lexeme::Symbol(Symbol::Colon), + .. + } => { + lexer.next()?; + Some(Type::parse(lexer, None)?) + } + _ => None, + }; + + Ok(Self { + location, + inner: literal, + yul_type, + }) + } + + /// + /// Converts the literal into its LLVM. + /// + pub fn into_llvm<'ctx, D>( + self, + context: &era_compiler_llvm_context::EraVMContext<'ctx, D>, + ) -> anyhow::Result> + where + D: era_compiler_llvm_context::EraVMDependency + Clone, + { + match self.inner { + LexicalLiteral::Boolean(inner) => { + let value = self + .yul_type + .unwrap_or_default() + .into_llvm(context) + .const_int( + match inner { + BooleanLiteral::False => 0, + BooleanLiteral::True => 1, + }, + false, + ) + .as_basic_value_enum(); + + let constant = match inner { + BooleanLiteral::False => num::BigUint::zero(), + BooleanLiteral::True => num::BigUint::one(), + }; + + Ok(era_compiler_llvm_context::EraVMArgument::new_with_constant( + value, constant, + )) + } + LexicalLiteral::Integer(inner) => { + let r#type = self.yul_type.unwrap_or_default().into_llvm(context); + let value = match inner { + IntegerLiteral::Decimal { ref inner } => r#type.const_int_from_string( + inner.as_str(), + inkwell::types::StringRadix::Decimal, + ), + IntegerLiteral::Hexadecimal { ref inner } => r#type.const_int_from_string( + &inner["0x".len()..], + inkwell::types::StringRadix::Hexadecimal, + ), + } + .expect("The value is valid") + .as_basic_value_enum(); + + let constant = match inner { + IntegerLiteral::Decimal { ref inner } => num::BigUint::from_str_radix( + inner.as_str(), + era_compiler_common::BASE_DECIMAL, + ), + IntegerLiteral::Hexadecimal { ref inner } => num::BigUint::from_str_radix( + &inner["0x".len()..], + era_compiler_common::BASE_HEXADECIMAL, + ), + } + .expect("Always valid"); + + Ok(era_compiler_llvm_context::EraVMArgument::new_with_constant( + value, constant, + )) + } + LexicalLiteral::String(inner) => { + let string = inner.inner; + let r#type = self.yul_type.unwrap_or_default().into_llvm(context); + + let mut hex_string = if inner.is_hexadecimal { + string.clone() + } else { + let mut hex_string = + String::with_capacity(era_compiler_common::BYTE_LENGTH_FIELD * 2); + let mut index = 0; + loop { + if index >= string.len() { + break; + } + + if string[index..].starts_with('\\') { + index += 1; + + if string[index..].starts_with('x') { + hex_string.push_str(&string[index + 1..index + 3]); + index += 3; + } else if string[index..].starts_with('u') { + let codepoint_str = &string[index + 1..index + 5]; + let codepoint = u32::from_str_radix( + codepoint_str, + era_compiler_common::BASE_HEXADECIMAL, + ) + .map_err(|error| { + anyhow::anyhow!( + "Invalid codepoint `{}`: {}", + codepoint_str, + error + ) + })?; + let unicode_char = char::from_u32(codepoint).ok_or_else(|| { + anyhow::anyhow!("Invalid codepoint {}", codepoint) + })?; + let mut unicode_bytes = vec![0u8; 3]; + unicode_char.encode_utf8(&mut unicode_bytes); + + for byte in unicode_bytes.into_iter() { + hex_string.push_str(format!("{:02x}", byte).as_str()); + } + index += 5; + } else if string[index..].starts_with('t') { + hex_string.push_str("09"); + index += 1; + } else if string[index..].starts_with('n') { + hex_string.push_str("0a"); + index += 1; + } else if string[index..].starts_with('r') { + hex_string.push_str("0d"); + index += 1; + } else if string[index..].starts_with('\n') { + index += 1; + } else { + hex_string + .push_str(format!("{:02x}", string.as_bytes()[index]).as_str()); + index += 1; + } + } else { + hex_string + .push_str(format!("{:02x}", string.as_bytes()[index]).as_str()); + index += 1; + } + } + hex_string + }; + + if hex_string.len() > era_compiler_common::BYTE_LENGTH_FIELD * 2 { + return Ok(era_compiler_llvm_context::EraVMArgument::new_with_original( + r#type.const_zero().as_basic_value_enum(), + string, + )); + } + + if hex_string.len() < era_compiler_common::BYTE_LENGTH_FIELD * 2 { + hex_string.push_str( + "0".repeat((era_compiler_common::BYTE_LENGTH_FIELD * 2) - hex_string.len()) + .as_str(), + ); + } + + let value = r#type + .const_int_from_string( + hex_string.as_str(), + inkwell::types::StringRadix::Hexadecimal, + ) + .expect("The value is valid") + .as_basic_value_enum(); + Ok(era_compiler_llvm_context::EraVMArgument::new_with_original( + value, string, + )) + } + } + } +} diff --git a/crates/solidity/src/yul/parser/statement/expression/mod.rs b/crates/solidity/src/yul/parser/statement/expression/mod.rs new file mode 100644 index 0000000..95752f4 --- /dev/null +++ b/crates/solidity/src/yul/parser/statement/expression/mod.rs @@ -0,0 +1,164 @@ +//! +//! The expression statement. +//! + +pub mod function_call; +pub mod literal; + +use std::collections::HashSet; + +use serde::Deserialize; +use serde::Serialize; + +use crate::yul::error::Error; +use crate::yul::lexer::token::lexeme::symbol::Symbol; +use crate::yul::lexer::token::lexeme::Lexeme; +use crate::yul::lexer::token::location::Location; +use crate::yul::lexer::token::Token; +use crate::yul::lexer::Lexer; +use crate::yul::parser::error::Error as ParserError; +use crate::yul::parser::identifier::Identifier; + +use self::function_call::FunctionCall; +use self::literal::Literal; + +/// +/// The Yul expression statement. +/// +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +pub enum Expression { + /// The function call subexpression. + FunctionCall(FunctionCall), + /// The identifier operand. + Identifier(Identifier), + /// The literal operand. + Literal(Literal), +} + +impl Expression { + /// + /// The element parser. + /// + pub fn parse(lexer: &mut Lexer, initial: Option) -> Result { + let token = crate::yul::parser::take_or_next(initial, lexer)?; + + let (location, identifier) = match token { + Token { + lexeme: Lexeme::Literal(_), + .. + } => return Ok(Self::Literal(Literal::parse(lexer, Some(token))?)), + Token { + location, + lexeme: Lexeme::Identifier(identifier), + .. + } => (location, identifier), + token => { + return Err(ParserError::InvalidToken { + location: token.location, + expected: vec!["{literal}", "{identifier}"], + found: token.lexeme.to_string(), + } + .into()); + } + }; + let length = identifier.inner.len(); + + match lexer.peek()? { + Token { + lexeme: Lexeme::Symbol(Symbol::ParenthesisLeft), + .. + } => { + lexer.next()?; + Ok(Self::FunctionCall(FunctionCall::parse( + lexer, + Some(Token::new(location, Lexeme::Identifier(identifier), length)), + )?)) + } + _ => Ok(Self::Identifier(Identifier::new( + location, + identifier.inner, + ))), + } + } + + /// + /// Get the list of missing deployable libraries. + /// + pub fn get_missing_libraries(&self) -> HashSet { + match self { + Self::FunctionCall(inner) => inner.get_missing_libraries(), + Self::Identifier(_) => HashSet::new(), + Self::Literal(_) => HashSet::new(), + } + } + + /// + /// Returns the statement location. + /// + pub fn location(&self) -> Location { + match self { + Self::FunctionCall(inner) => inner.location, + Self::Identifier(inner) => inner.location, + Self::Literal(inner) => inner.location, + } + } + + /// + /// Converts the expression into an LLVM value. + /// + pub fn into_llvm<'ctx, D>( + self, + context: &mut era_compiler_llvm_context::EraVMContext<'ctx, D>, + ) -> anyhow::Result>> + where + D: era_compiler_llvm_context::EraVMDependency + Clone, + { + match self { + Self::Literal(literal) => literal + .clone() + .into_llvm(context) + .map_err(|error| { + anyhow::anyhow!( + "{} Invalid literal `{}`: {}", + literal.location, + literal.inner.to_string(), + error + ) + }) + .map(Some), + Self::Identifier(identifier) => { + let pointer = context + .current_function() + .borrow() + .get_stack_pointer(identifier.inner.as_str()) + .ok_or_else(|| { + anyhow::anyhow!( + "{} Undeclared variable `{}`", + identifier.location, + identifier.inner, + ) + })?; + + let constant = context + .current_function() + .borrow() + .yul() + .get_constant(identifier.inner.as_str()); + + let value = context.build_load(pointer, identifier.inner.as_str())?; + + match constant { + Some(constant) => Ok(Some( + era_compiler_llvm_context::EraVMArgument::new_with_constant( + value, constant, + ), + )), + None => Ok(Some(value.into())), + } + } + Self::FunctionCall(call) => Ok(call + .into_llvm(context)? + .map(era_compiler_llvm_context::EraVMArgument::new)), + } + } +} diff --git a/crates/solidity/src/yul/parser/statement/for_loop.rs b/crates/solidity/src/yul/parser/statement/for_loop.rs new file mode 100644 index 0000000..e1509b8 --- /dev/null +++ b/crates/solidity/src/yul/parser/statement/for_loop.rs @@ -0,0 +1,122 @@ +//! +//! The for-loop statement. +//! + +use std::collections::HashSet; + +use serde::Deserialize; +use serde::Serialize; + +use crate::yul::error::Error; +use crate::yul::lexer::token::location::Location; +use crate::yul::lexer::token::Token; +use crate::yul::lexer::Lexer; +use crate::yul::parser::statement::block::Block; +use crate::yul::parser::statement::expression::Expression; + +/// +/// The Yul for-loop statement. +/// +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +pub struct ForLoop { + /// The location. + pub location: Location, + /// The index variables initialization block. + pub initializer: Block, + /// The continue condition block. + pub condition: Expression, + /// The index variables mutating block. + pub finalizer: Block, + /// The loop body. + pub body: Block, +} + +impl ForLoop { + /// + /// The element parser. + /// + pub fn parse(lexer: &mut Lexer, initial: Option) -> Result { + let token = crate::yul::parser::take_or_next(initial, lexer)?; + let location = token.location; + + let initializer = Block::parse(lexer, Some(token))?; + + let condition = Expression::parse(lexer, None)?; + + let finalizer = Block::parse(lexer, None)?; + + let body = Block::parse(lexer, None)?; + + Ok(Self { + location, + initializer, + condition, + finalizer, + body, + }) + } + + /// + /// Get the list of missing deployable libraries. + /// + pub fn get_missing_libraries(&self) -> HashSet { + let mut libraries = self.initializer.get_missing_libraries(); + libraries.extend(self.condition.get_missing_libraries()); + libraries.extend(self.finalizer.get_missing_libraries()); + libraries.extend(self.body.get_missing_libraries()); + libraries + } +} + +impl era_compiler_llvm_context::EraVMWriteLLVM for ForLoop +where + D: era_compiler_llvm_context::EraVMDependency + Clone, +{ + fn into_llvm( + self, + context: &mut era_compiler_llvm_context::EraVMContext, + ) -> anyhow::Result<()> { + self.initializer.into_llvm(context)?; + + let condition_block = context.append_basic_block("for_condition"); + let body_block = context.append_basic_block("for_body"); + let increment_block = context.append_basic_block("for_increment"); + let join_block = context.append_basic_block("for_join"); + + context.build_unconditional_branch(condition_block); + context.set_basic_block(condition_block); + let condition = self + .condition + .into_llvm(context)? + .expect("Always exists") + .to_llvm() + .into_int_value(); + let condition = context.builder().build_int_z_extend_or_bit_cast( + condition, + context.field_type(), + "for_condition_extended", + )?; + let condition = context.builder().build_int_compare( + inkwell::IntPredicate::NE, + condition, + context.field_const(0), + "for_condition_compared", + )?; + context.build_conditional_branch(condition, body_block, join_block)?; + + context.push_loop(body_block, increment_block, join_block); + + context.set_basic_block(body_block); + self.body.into_llvm(context)?; + context.build_unconditional_branch(increment_block); + + context.set_basic_block(increment_block); + self.finalizer.into_llvm(context)?; + context.build_unconditional_branch(condition_block); + + context.pop_loop(); + context.set_basic_block(join_block); + + Ok(()) + } +} diff --git a/crates/solidity/src/yul/parser/statement/function_definition.rs b/crates/solidity/src/yul/parser/statement/function_definition.rs new file mode 100644 index 0000000..ed8c378 --- /dev/null +++ b/crates/solidity/src/yul/parser/statement/function_definition.rs @@ -0,0 +1,720 @@ +//! +//! The function definition statement. +//! + +use std::collections::BTreeSet; +use std::collections::HashSet; + +use inkwell::types::BasicType; +use serde::Deserialize; +use serde::Serialize; + +use crate::yul::error::Error; +use crate::yul::lexer::token::lexeme::symbol::Symbol; +use crate::yul::lexer::token::lexeme::Lexeme; +use crate::yul::lexer::token::location::Location; +use crate::yul::lexer::token::Token; +use crate::yul::lexer::Lexer; +use crate::yul::parser::error::Error as ParserError; +use crate::yul::parser::identifier::Identifier; +use crate::yul::parser::statement::block::Block; +use crate::yul::parser::statement::expression::function_call::name::Name as FunctionName; + +/// +/// The function definition statement. +/// +/// All functions are translated in two steps: +/// 1. The hoisted declaration +/// 2. The definition, which now has the access to all function signatures +/// +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +pub struct FunctionDefinition { + /// The location. + pub location: Location, + /// The function identifier. + pub identifier: String, + /// The function formal arguments. + pub arguments: Vec, + /// The function return variables. + pub result: Vec, + /// The function body block. + pub body: Block, + /// The function LLVM attributes encoded in the identifier. + pub attributes: BTreeSet, +} + +impl FunctionDefinition { + /// The LLVM attribute section prefix. + pub const LLVM_ATTRIBUTE_PREFIX: &'static str = "$llvm_"; + + /// The LLVM attribute section suffix. + pub const LLVM_ATTRIBUTE_SUFFIX: &'static str = "_llvm$"; + + /// + /// The element parser. + /// + pub fn parse(lexer: &mut Lexer, initial: Option) -> Result { + let token = crate::yul::parser::take_or_next(initial, lexer)?; + + let (location, identifier) = match token { + Token { + lexeme: Lexeme::Identifier(identifier), + location, + .. + } => (location, identifier), + token => { + return Err(ParserError::InvalidToken { + location: token.location, + expected: vec!["{identifier}"], + found: token.lexeme.to_string(), + } + .into()); + } + }; + let identifier = Identifier::new(location, identifier.inner); + + match FunctionName::from(identifier.inner.as_str()) { + FunctionName::UserDefined(_) => {} + _function_name => { + return Err(ParserError::ReservedIdentifier { + location, + identifier: identifier.inner, + } + .into()) + } + } + + match lexer.next()? { + Token { + lexeme: Lexeme::Symbol(Symbol::ParenthesisLeft), + .. + } => {} + token => { + return Err(ParserError::InvalidToken { + location: token.location, + expected: vec!["("], + found: token.lexeme.to_string(), + } + .into()); + } + } + + let (mut arguments, next) = Identifier::parse_typed_list(lexer, None)?; + if identifier + .inner + .contains(era_compiler_llvm_context::EraVMFunction::ZKSYNC_NEAR_CALL_ABI_PREFIX) + { + if arguments.is_empty() { + return Err(ParserError::InvalidNumberOfArguments { + location, + identifier: identifier.inner, + expected: 1, + found: arguments.len(), + } + .into()); + } + + arguments.remove(0); + } + if identifier.inner.contains( + era_compiler_llvm_context::EraVMFunction::ZKSYNC_NEAR_CALL_ABI_EXCEPTION_HANDLER, + ) && !arguments.is_empty() + { + return Err(ParserError::InvalidNumberOfArguments { + location, + identifier: identifier.inner, + expected: 0, + found: arguments.len(), + } + .into()); + } + + match crate::yul::parser::take_or_next(next, lexer)? { + Token { + lexeme: Lexeme::Symbol(Symbol::ParenthesisRight), + .. + } => {} + token => { + return Err(ParserError::InvalidToken { + location: token.location, + expected: vec![")"], + found: token.lexeme.to_string(), + } + .into()); + } + } + + let (result, next) = match lexer.peek()? { + Token { + lexeme: Lexeme::Symbol(Symbol::Arrow), + .. + } => { + lexer.next()?; + Identifier::parse_typed_list(lexer, None)? + } + Token { + lexeme: Lexeme::Symbol(Symbol::BracketCurlyLeft), + .. + } => (vec![], None), + token => { + return Err(ParserError::InvalidToken { + location: token.location, + expected: vec!["->", "{"], + found: token.lexeme.to_string(), + } + .into()); + } + }; + + let body = Block::parse(lexer, next)?; + + let attributes = Self::get_llvm_attributes(&identifier)?; + + Ok(Self { + location, + identifier: identifier.inner, + arguments, + result, + body, + attributes, + }) + } + + /// + /// Gets the list of missing deployable libraries. + /// + pub fn get_missing_libraries(&self) -> HashSet { + self.body.get_missing_libraries() + } + + /// + /// Gets the list of LLVM attributes provided in the function name. + /// + pub fn get_llvm_attributes( + identifier: &Identifier, + ) -> Result, Error> { + let mut valid_attributes = BTreeSet::new(); + + let llvm_begin = identifier.inner.find(Self::LLVM_ATTRIBUTE_PREFIX); + let llvm_end = identifier.inner.find(Self::LLVM_ATTRIBUTE_SUFFIX); + let attribute_string = if let (Some(llvm_begin), Some(llvm_end)) = (llvm_begin, llvm_end) { + if llvm_begin < llvm_end { + &identifier.inner[llvm_begin + Self::LLVM_ATTRIBUTE_PREFIX.len()..llvm_end] + } else { + return Ok(valid_attributes); + } + } else { + return Ok(valid_attributes); + }; + + let mut invalid_attributes = BTreeSet::new(); + for value in attribute_string.split('_') { + match era_compiler_llvm_context::EraVMAttribute::try_from(value) { + Ok(attribute) => valid_attributes.insert(attribute), + Err(value) => invalid_attributes.insert(value), + }; + } + + if !invalid_attributes.is_empty() { + return Err(ParserError::InvalidAttributes { + location: identifier.location, + values: invalid_attributes, + } + .into()); + } + + Ok(valid_attributes) + } +} + +impl era_compiler_llvm_context::EraVMWriteLLVM for FunctionDefinition +where + D: era_compiler_llvm_context::EraVMDependency + Clone, +{ + fn declare( + &mut self, + context: &mut era_compiler_llvm_context::EraVMContext, + ) -> anyhow::Result<()> { + let argument_types: Vec<_> = self + .arguments + .iter() + .map(|argument| { + let yul_type = argument.r#type.to_owned().unwrap_or_default(); + yul_type.into_llvm(context).as_basic_type_enum() + }) + .collect(); + + let function_type = context.function_type( + argument_types, + self.result.len(), + self.identifier + .starts_with(era_compiler_llvm_context::EraVMFunction::ZKSYNC_NEAR_CALL_ABI_PREFIX), + ); + + let function = context.add_function( + self.identifier.as_str(), + function_type, + self.result.len(), + Some(inkwell::module::Linkage::Private), + )?; + era_compiler_llvm_context::EraVMFunction::set_attributes( + context.llvm(), + function.borrow().declaration(), + self.attributes.clone().into_iter().collect(), + true, + ); + function + .borrow_mut() + .set_yul_data(era_compiler_llvm_context::EraVMFunctionYulData::default()); + + Ok(()) + } + + fn into_llvm( + mut self, + context: &mut era_compiler_llvm_context::EraVMContext, + ) -> anyhow::Result<()> { + context.set_current_function(self.identifier.as_str())?; + let r#return = context.current_function().borrow().r#return(); + + context.set_basic_block(context.current_function().borrow().entry_block()); + match r#return { + era_compiler_llvm_context::EraVMFunctionReturn::None => {} + era_compiler_llvm_context::EraVMFunctionReturn::Primitive { pointer } => { + let identifier = self.result.pop().expect("Always exists"); + let r#type = identifier.r#type.unwrap_or_default(); + context.build_store(pointer, r#type.into_llvm(context).const_zero())?; + context + .current_function() + .borrow_mut() + .insert_stack_pointer(identifier.inner, pointer); + } + era_compiler_llvm_context::EraVMFunctionReturn::Compound { pointer, .. } => { + for (index, identifier) in self.result.into_iter().enumerate() { + let r#type = identifier.r#type.unwrap_or_default().into_llvm(context); + let pointer = context.build_gep( + pointer, + &[ + context.field_const(0), + context + .integer_type(era_compiler_common::BIT_LENGTH_X32) + .const_int(index as u64, false), + ], + context.field_type(), + format!("return_{index}_gep_pointer").as_str(), + ); + context.build_store(pointer, r#type.const_zero())?; + context + .current_function() + .borrow_mut() + .insert_stack_pointer(identifier.inner.clone(), pointer); + } + } + }; + + let argument_types: Vec<_> = self + .arguments + .iter() + .map(|argument| { + let yul_type = argument.r#type.to_owned().unwrap_or_default(); + yul_type.into_llvm(context) + }) + .collect(); + for (mut index, argument) in self.arguments.iter().enumerate() { + let pointer = context.build_alloca(argument_types[index], argument.inner.as_str()); + context + .current_function() + .borrow_mut() + .insert_stack_pointer(argument.inner.clone(), pointer); + if self + .identifier + .starts_with(era_compiler_llvm_context::EraVMFunction::ZKSYNC_NEAR_CALL_ABI_PREFIX) + && matches!( + context.current_function().borrow().r#return(), + era_compiler_llvm_context::EraVMFunctionReturn::Compound { .. } + ) + && context.is_system_mode() + { + index += 1; + } + context.build_store( + pointer, + context.current_function().borrow().get_nth_param(index), + )?; + } + + self.body.into_llvm(context)?; + match context + .basic_block() + .get_last_instruction() + .map(|instruction| instruction.get_opcode()) + { + Some(inkwell::values::InstructionOpcode::Br) => {} + Some(inkwell::values::InstructionOpcode::Switch) => {} + _ => context + .build_unconditional_branch(context.current_function().borrow().return_block()), + } + + context.set_basic_block(context.current_function().borrow().return_block()); + match context.current_function().borrow().r#return() { + era_compiler_llvm_context::EraVMFunctionReturn::None => { + context.build_return(None); + } + era_compiler_llvm_context::EraVMFunctionReturn::Primitive { pointer } => { + let return_value = context.build_load(pointer, "return_value")?; + context.build_return(Some(&return_value)); + } + era_compiler_llvm_context::EraVMFunctionReturn::Compound { pointer, .. } + if context.current_function().borrow().name().starts_with( + era_compiler_llvm_context::EraVMFunction::ZKSYNC_NEAR_CALL_ABI_PREFIX, + ) => + { + context.build_return(Some(&pointer.value)); + } + era_compiler_llvm_context::EraVMFunctionReturn::Compound { pointer, .. } => { + let return_value = context.build_load(pointer, "return_value")?; + context.build_return(Some(&return_value)); + } + } + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use std::collections::BTreeSet; + + use crate::yul::lexer::token::location::Location; + use crate::yul::lexer::Lexer; + use crate::yul::parser::error::Error; + use crate::yul::parser::statement::object::Object; + + #[test] + fn error_invalid_token_identifier() { + let input = r#" +object "Test" { + code { + { + return(0, 0) + } + } + object "Test_deployed" { + code { + { + return(0, 0) + } + + function 256() -> result { + result := 42 + } + } + } +} + "#; + + let mut lexer = Lexer::new(input.to_owned()); + let result = Object::parse(&mut lexer, None); + assert_eq!( + result, + Err(Error::InvalidToken { + location: Location::new(14, 22), + expected: vec!["{identifier}"], + found: "256".to_owned(), + } + .into()) + ); + } + + #[test] + fn error_invalid_token_parenthesis_left() { + let input = r#" +object "Test" { + code { + { + return(0, 0) + } + } + object "Test_deployed" { + code { + { + return(0, 0) + } + + function test{) -> result { + result := 42 + } + } + } +} + "#; + + let mut lexer = Lexer::new(input.to_owned()); + let result = Object::parse(&mut lexer, None); + assert_eq!( + result, + Err(Error::InvalidToken { + location: Location::new(14, 26), + expected: vec!["("], + found: "{".to_owned(), + } + .into()) + ); + } + + #[test] + fn error_invalid_token_parenthesis_right() { + let input = r#" +object "Test" { + code { + { + return(0, 0) + } + } + object "Test_deployed" { + code { + { + return(0, 0) + } + + function test(} -> result { + result := 42 + } + } + } +} + "#; + + let mut lexer = Lexer::new(input.to_owned()); + let result = Object::parse(&mut lexer, None); + assert_eq!( + result, + Err(Error::InvalidToken { + location: Location::new(14, 27), + expected: vec![")"], + found: "}".to_owned(), + } + .into()) + ); + } + + #[test] + fn error_invalid_token_arrow_or_bracket_curly_left() { + let input = r#" +object "Test" { + code { + { + return(0, 0) + } + } + object "Test_deployed" { + code { + { + return(0, 0) + } + + function test() := result { + result := 42 + } + } + } +} + "#; + + let mut lexer = Lexer::new(input.to_owned()); + let result = Object::parse(&mut lexer, None); + assert_eq!( + result, + Err(Error::InvalidToken { + location: Location::new(14, 29), + expected: vec!["->", "{"], + found: ":=".to_owned(), + } + .into()) + ); + } + + #[test] + fn error_invalid_number_of_arguments_near_call_abi() { + let input = r#" +object "Test" { + code { + { + return(0, 0) + } + } + object "Test_deployed" { + code { + { + return(0, 0) + } + + function ZKSYNC_NEAR_CALL_test() -> result { + result := 42 + } + } + } +} + "#; + + let mut lexer = Lexer::new(input.to_owned()); + let result = Object::parse(&mut lexer, None); + assert_eq!( + result, + Err(Error::InvalidNumberOfArguments { + location: Location::new(14, 22), + identifier: "ZKSYNC_NEAR_CALL_test".to_owned(), + expected: 1, + found: 0, + } + .into()) + ); + } + + #[test] + fn error_invalid_number_of_arguments_near_call_abi_catch() { + let input = r#" +object "Test" { + code { + { + return(0, 0) + } + } + object "Test_deployed" { + code { + { + return(0, 0) + } + + function ZKSYNC_CATCH_NEAR_CALL(length) { + revert(0, length) + } + } + } +} + "#; + + let mut lexer = Lexer::new(input.to_owned()); + let result = Object::parse(&mut lexer, None); + assert_eq!( + result, + Err(Error::InvalidNumberOfArguments { + location: Location::new(14, 22), + identifier: "ZKSYNC_CATCH_NEAR_CALL".to_owned(), + expected: 0, + found: 1, + } + .into()) + ); + } + + #[test] + fn error_reserved_identifier() { + let input = r#" +object "Test" { + code { + { + return(0, 0) + } + } + object "Test_deployed" { + code { + { + return(0, 0) + } + + function basefee() -> result { + result := 42 + } + } + } +} + "#; + + let mut lexer = Lexer::new(input.to_owned()); + let result = Object::parse(&mut lexer, None); + assert_eq!( + result, + Err(Error::ReservedIdentifier { + location: Location::new(14, 22), + identifier: "basefee".to_owned() + } + .into()) + ); + } + + #[test] + fn error_invalid_attributes_single() { + let input = r#" +object "Test" { + code { + { + return(0, 0) + } + } + object "Test_deployed" { + code { + { + return(0, 0) + } + + function test_$llvm_UnknownAttribute_llvm$_test() -> result { + result := 42 + } + } + } +} + "#; + let mut invalid_attributes = BTreeSet::new(); + invalid_attributes.insert("UnknownAttribute".to_owned()); + + let mut lexer = Lexer::new(input.to_owned()); + let result = Object::parse(&mut lexer, None); + assert_eq!( + result, + Err(Error::InvalidAttributes { + location: Location::new(14, 22), + values: invalid_attributes, + } + .into()) + ); + } + + #[test] + fn error_invalid_attributes_multiple_repeated() { + let input = r#" +object "Test" { + code { + { + return(0, 0) + } + } + object "Test_deployed" { + code { + { + return(0, 0) + } + + function test_$llvm_UnknownAttribute1_UnknownAttribute1_UnknownAttribute2_llvm$_test() -> result { + result := 42 + } + } + } +} + "#; + let mut invalid_attributes = BTreeSet::new(); + invalid_attributes.insert("UnknownAttribute1".to_owned()); + invalid_attributes.insert("UnknownAttribute2".to_owned()); + + let mut lexer = Lexer::new(input.to_owned()); + let result = Object::parse(&mut lexer, None); + assert_eq!( + result, + Err(Error::InvalidAttributes { + location: Location::new(14, 22), + values: invalid_attributes, + } + .into()) + ); + } +} diff --git a/crates/solidity/src/yul/parser/statement/if_conditional.rs b/crates/solidity/src/yul/parser/statement/if_conditional.rs new file mode 100644 index 0000000..df725ba --- /dev/null +++ b/crates/solidity/src/yul/parser/statement/if_conditional.rs @@ -0,0 +1,94 @@ +//! +//! The if-conditional statement. +//! + +use std::collections::HashSet; + +use serde::Deserialize; +use serde::Serialize; + +use crate::yul::error::Error; +use crate::yul::lexer::token::location::Location; +use crate::yul::lexer::token::Token; +use crate::yul::lexer::Lexer; +use crate::yul::parser::statement::block::Block; +use crate::yul::parser::statement::expression::Expression; + +/// +/// The Yul if-conditional statement. +/// +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +pub struct IfConditional { + /// The location. + pub location: Location, + /// The condition expression. + pub condition: Expression, + /// The conditional block. + pub block: Block, +} + +impl IfConditional { + /// + /// The element parser. + /// + pub fn parse(lexer: &mut Lexer, initial: Option) -> Result { + let token = crate::yul::parser::take_or_next(initial, lexer)?; + let location = token.location; + + let condition = Expression::parse(lexer, Some(token))?; + + let block = Block::parse(lexer, None)?; + + Ok(Self { + location, + condition, + block, + }) + } + + /// + /// Get the list of missing deployable libraries. + /// + pub fn get_missing_libraries(&self) -> HashSet { + let mut libraries = self.condition.get_missing_libraries(); + libraries.extend(self.block.get_missing_libraries()); + libraries + } +} + +impl era_compiler_llvm_context::EraVMWriteLLVM for IfConditional +where + D: era_compiler_llvm_context::EraVMDependency + Clone, +{ + fn into_llvm( + self, + context: &mut era_compiler_llvm_context::EraVMContext, + ) -> anyhow::Result<()> { + let condition = self + .condition + .into_llvm(context)? + .expect("Always exists") + .to_llvm() + .into_int_value(); + let condition = context.builder().build_int_z_extend_or_bit_cast( + condition, + context.field_type(), + "if_condition_extended", + )?; + let condition = context.builder().build_int_compare( + inkwell::IntPredicate::NE, + condition, + context.field_const(0), + "if_condition_compared", + )?; + let main_block = context.append_basic_block("if_main"); + let join_block = context.append_basic_block("if_join"); + context.build_conditional_branch(condition, main_block, join_block)?; + context.set_basic_block(main_block); + self.block.into_llvm(context)?; + context.build_unconditional_branch(join_block); + context.set_basic_block(join_block); + + Ok(()) + } +} diff --git a/crates/solidity/src/yul/parser/statement/mod.rs b/crates/solidity/src/yul/parser/statement/mod.rs new file mode 100644 index 0000000..a022b90 --- /dev/null +++ b/crates/solidity/src/yul/parser/statement/mod.rs @@ -0,0 +1,189 @@ +//! +//! The block statement. +//! + +pub mod assignment; +pub mod block; +pub mod code; +pub mod expression; +pub mod for_loop; +pub mod function_definition; +pub mod if_conditional; +pub mod object; +pub mod switch; +pub mod variable_declaration; + +use std::collections::HashSet; + +use serde::Deserialize; +use serde::Serialize; + +use crate::yul::error::Error; +use crate::yul::lexer::token::lexeme::keyword::Keyword; +use crate::yul::lexer::token::lexeme::Lexeme; +use crate::yul::lexer::token::location::Location; +use crate::yul::lexer::token::Token; +use crate::yul::lexer::Lexer; +use crate::yul::parser::error::Error as ParserError; + +use self::assignment::Assignment; +use self::block::Block; +use self::code::Code; +use self::expression::Expression; +use self::for_loop::ForLoop; +use self::function_definition::FunctionDefinition; +use self::if_conditional::IfConditional; +use self::object::Object; +use self::switch::Switch; +use self::variable_declaration::VariableDeclaration; + +/// +/// The Yul block statement. +/// +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +pub enum Statement { + /// The object element. + Object(Object), + /// The code element. + Code(Code), + /// The code block. + Block(Block), + /// The expression. + Expression(Expression), + /// The `function` statement. + FunctionDefinition(FunctionDefinition), + /// The `let` statement. + VariableDeclaration(VariableDeclaration), + /// The `:=` existing variables reassignment statement. + Assignment(Assignment), + /// The `if` statement. + IfConditional(IfConditional), + /// The `switch` statement. + Switch(Switch), + /// The `for` statement. + ForLoop(ForLoop), + /// The `continue` statement. + Continue(Location), + /// The `break` statement. + Break(Location), + /// The `leave` statement. + Leave(Location), +} + +impl Statement { + /// + /// The element parser. + /// + pub fn parse( + lexer: &mut Lexer, + initial: Option, + ) -> Result<(Self, Option), Error> { + let token = crate::yul::parser::take_or_next(initial, lexer)?; + + match token { + token @ Token { + lexeme: Lexeme::Keyword(Keyword::Object), + .. + } => Ok((Statement::Object(Object::parse(lexer, Some(token))?), None)), + Token { + lexeme: Lexeme::Keyword(Keyword::Code), + .. + } => Ok((Statement::Code(Code::parse(lexer, None)?), None)), + Token { + lexeme: Lexeme::Keyword(Keyword::Function), + .. + } => Ok(( + Statement::FunctionDefinition(FunctionDefinition::parse(lexer, None)?), + None, + )), + Token { + lexeme: Lexeme::Keyword(Keyword::Let), + .. + } => { + let (statement, next) = VariableDeclaration::parse(lexer, None)?; + Ok((Statement::VariableDeclaration(statement), next)) + } + Token { + lexeme: Lexeme::Keyword(Keyword::If), + .. + } => Ok(( + Statement::IfConditional(IfConditional::parse(lexer, None)?), + None, + )), + Token { + lexeme: Lexeme::Keyword(Keyword::Switch), + .. + } => Ok((Statement::Switch(Switch::parse(lexer, None)?), None)), + Token { + lexeme: Lexeme::Keyword(Keyword::For), + .. + } => Ok((Statement::ForLoop(ForLoop::parse(lexer, None)?), None)), + Token { + lexeme: Lexeme::Keyword(Keyword::Continue), + location, + .. + } => Ok((Statement::Continue(location), None)), + Token { + lexeme: Lexeme::Keyword(Keyword::Break), + location, + .. + } => Ok((Statement::Break(location), None)), + Token { + lexeme: Lexeme::Keyword(Keyword::Leave), + location, + .. + } => Ok((Statement::Leave(location), None)), + token => Err(ParserError::InvalidToken { + location: token.location, + expected: vec![ + "object", "code", "function", "let", "if", "switch", "for", "continue", + "break", "leave", + ], + found: token.lexeme.to_string(), + } + .into()), + } + } + + /// + /// Get the list of missing deployable libraries. + /// + pub fn get_missing_libraries(&self) -> HashSet { + match self { + Self::Object(inner) => inner.get_missing_libraries(), + Self::Code(inner) => inner.get_missing_libraries(), + Self::Block(inner) => inner.get_missing_libraries(), + Self::Expression(inner) => inner.get_missing_libraries(), + Self::FunctionDefinition(inner) => inner.get_missing_libraries(), + Self::VariableDeclaration(inner) => inner.get_missing_libraries(), + Self::Assignment(inner) => inner.get_missing_libraries(), + Self::IfConditional(inner) => inner.get_missing_libraries(), + Self::Switch(inner) => inner.get_missing_libraries(), + Self::ForLoop(inner) => inner.get_missing_libraries(), + Self::Continue(_) => HashSet::new(), + Self::Break(_) => HashSet::new(), + Self::Leave(_) => HashSet::new(), + } + } + + /// + /// Returns the statement location. + /// + pub fn location(&self) -> Location { + match self { + Self::Object(inner) => inner.location, + Self::Code(inner) => inner.location, + Self::Block(inner) => inner.location, + Self::Expression(inner) => inner.location(), + Self::FunctionDefinition(inner) => inner.location, + Self::VariableDeclaration(inner) => inner.location, + Self::Assignment(inner) => inner.location, + Self::IfConditional(inner) => inner.location, + Self::Switch(inner) => inner.location, + Self::ForLoop(inner) => inner.location, + Self::Continue(location) => *location, + Self::Break(location) => *location, + Self::Leave(location) => *location, + } + } +} diff --git a/crates/solidity/src/yul/parser/statement/object.rs b/crates/solidity/src/yul/parser/statement/object.rs new file mode 100644 index 0000000..16025c0 --- /dev/null +++ b/crates/solidity/src/yul/parser/statement/object.rs @@ -0,0 +1,424 @@ +//! +//! The YUL object. +//! + +use std::collections::HashSet; + +use serde::Deserialize; +use serde::Serialize; + +use crate::yul::error::Error; +use crate::yul::lexer::token::lexeme::keyword::Keyword; +use crate::yul::lexer::token::lexeme::literal::Literal; +use crate::yul::lexer::token::lexeme::symbol::Symbol; +use crate::yul::lexer::token::lexeme::Lexeme; +use crate::yul::lexer::token::location::Location; +use crate::yul::lexer::token::Token; +use crate::yul::lexer::Lexer; +use crate::yul::parser::error::Error as ParserError; +use crate::yul::parser::statement::code::Code; + +/// +/// The upper-level YUL object, representing the deploy code. +/// +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +pub struct Object { + /// The location. + pub location: Location, + /// The identifier. + pub identifier: String, + /// The code. + pub code: Code, + /// The optional inner object, representing the runtime code. + pub inner_object: Option>, + /// The factory dependency objects, which are represented by nested Yul object. The nested + /// objects are duplicates of the upper-level objects describing the dependencies, so only + /// their identifiers are preserved. The identifiers are used to address upper-level objects. + pub factory_dependencies: HashSet, +} + +impl Object { + /// + /// The element parser. + /// + pub fn parse(lexer: &mut Lexer, initial: Option) -> Result { + let token = crate::yul::parser::take_or_next(initial, lexer)?; + + let location = match token { + Token { + lexeme: Lexeme::Keyword(Keyword::Object), + location, + .. + } => location, + token => { + return Err(ParserError::InvalidToken { + location: token.location, + expected: vec!["object"], + found: token.lexeme.to_string(), + } + .into()); + } + }; + + let identifier = match lexer.next()? { + Token { + lexeme: Lexeme::Literal(Literal::String(literal)), + .. + } => literal.inner, + token => { + return Err(ParserError::InvalidToken { + location: token.location, + expected: vec!["{string}"], + found: token.lexeme.to_string(), + } + .into()); + } + }; + let is_runtime_code = identifier.ends_with("_deployed"); + + match lexer.next()? { + Token { + lexeme: Lexeme::Symbol(Symbol::BracketCurlyLeft), + .. + } => {} + token => { + return Err(ParserError::InvalidToken { + location: token.location, + expected: vec!["{"], + found: token.lexeme.to_string(), + } + .into()); + } + } + + let code = Code::parse(lexer, None)?; + let mut inner_object = None; + let mut factory_dependencies = HashSet::new(); + + if !is_runtime_code { + inner_object = match lexer.peek()? { + Token { + lexeme: Lexeme::Keyword(Keyword::Object), + .. + } => { + let mut object = Self::parse(lexer, None)?; + + if format!("{identifier}_deployed") != object.identifier { + return Err(ParserError::InvalidObjectName { + location: object.location, + expected: format!("{identifier}_deployed"), + found: object.identifier, + } + .into()); + } + + factory_dependencies.extend(object.factory_dependencies.drain()); + Some(Box::new(object)) + } + _ => None, + }; + + if let Token { + lexeme: Lexeme::Identifier(identifier), + .. + } = lexer.peek()? + { + if identifier.inner.as_str() == "data" { + let _data = lexer.next()?; + let _identifier = lexer.next()?; + let _metadata = lexer.next()?; + } + }; + } + + loop { + match lexer.next()? { + Token { + lexeme: Lexeme::Symbol(Symbol::BracketCurlyRight), + .. + } => break, + token @ Token { + lexeme: Lexeme::Keyword(Keyword::Object), + .. + } => { + let dependency = Self::parse(lexer, Some(token))?; + factory_dependencies.insert(dependency.identifier); + } + Token { + lexeme: Lexeme::Identifier(identifier), + .. + } if identifier.inner.as_str() == "data" => { + let _identifier = lexer.next()?; + let _metadata = lexer.next()?; + } + token => { + return Err(ParserError::InvalidToken { + location: token.location, + expected: vec!["object", "}"], + found: token.lexeme.to_string(), + } + .into()); + } + } + } + + Ok(Self { + location, + identifier, + code, + inner_object, + factory_dependencies, + }) + } + + /// + /// Get the list of missing deployable libraries. + /// + pub fn get_missing_libraries(&self) -> HashSet { + let mut missing_libraries = self.code.get_missing_libraries(); + if let Some(inner_object) = &self.inner_object { + missing_libraries.extend(inner_object.get_missing_libraries()); + } + missing_libraries + } +} + +impl era_compiler_llvm_context::EraVMWriteLLVM for Object +where + D: era_compiler_llvm_context::EraVMDependency + Clone, +{ + fn declare( + &mut self, + context: &mut era_compiler_llvm_context::EraVMContext, + ) -> anyhow::Result<()> { + let mut entry = era_compiler_llvm_context::EraVMEntryFunction::default(); + entry.declare(context)?; + + let mut runtime = era_compiler_llvm_context::EraVMRuntime::new( + era_compiler_llvm_context::EraVMAddressSpace::Heap, + ); + runtime.declare(context)?; + + era_compiler_llvm_context::EraVMDeployCodeFunction::new( + era_compiler_llvm_context::EraVMDummyLLVMWritable::default(), + ) + .declare(context)?; + era_compiler_llvm_context::EraVMRuntimeCodeFunction::new( + era_compiler_llvm_context::EraVMDummyLLVMWritable::default(), + ) + .declare(context)?; + + for name in [ + era_compiler_llvm_context::EraVMRuntime::FUNCTION_DEPLOY_CODE, + era_compiler_llvm_context::EraVMRuntime::FUNCTION_RUNTIME_CODE, + era_compiler_llvm_context::EraVMRuntime::FUNCTION_ENTRY, + ] + .into_iter() + { + context + .get_function(name) + .expect("Always exists") + .borrow_mut() + .set_yul_data(era_compiler_llvm_context::EraVMFunctionYulData::default()); + } + + entry.into_llvm(context)?; + + Ok(()) + } + + fn into_llvm( + self, + context: &mut era_compiler_llvm_context::EraVMContext, + ) -> anyhow::Result<()> { + if self.identifier.ends_with("_deployed") { + era_compiler_llvm_context::EraVMRuntimeCodeFunction::new(self.code) + .into_llvm(context)?; + } else { + era_compiler_llvm_context::EraVMDeployCodeFunction::new(self.code) + .into_llvm(context)?; + } + + match self.inner_object { + Some(object) => { + object.into_llvm(context)?; + } + None => { + let runtime = era_compiler_llvm_context::EraVMRuntime::new( + era_compiler_llvm_context::EraVMAddressSpace::Heap, + ); + runtime.into_llvm(context)?; + } + } + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use crate::yul::lexer::token::location::Location; + use crate::yul::lexer::Lexer; + use crate::yul::parser::error::Error; + use crate::yul::parser::statement::object::Object; + + #[test] + fn error_invalid_token_object() { + let input = r#" +class "Test" { + code { + { + return(0, 0) + } + } + object "Test_deployed" { + code { + { + return(0, 0) + } + } + } +} + "#; + + let mut lexer = Lexer::new(input.to_owned()); + let result = Object::parse(&mut lexer, None); + assert_eq!( + result, + Err(Error::InvalidToken { + location: Location::new(2, 1), + expected: vec!["object"], + found: "class".to_owned(), + } + .into()) + ); + } + + #[test] + fn error_invalid_token_identifier() { + let input = r#" +object 256 { + code { + { + return(0, 0) + } + } + object "Test_deployed" { + code { + { + return(0, 0) + } + } + } +} + "#; + + let mut lexer = Lexer::new(input.to_owned()); + let result = Object::parse(&mut lexer, None); + assert_eq!( + result, + Err(Error::InvalidToken { + location: Location::new(2, 8), + expected: vec!["{string}"], + found: "256".to_owned(), + } + .into()) + ); + } + + #[test] + fn error_invalid_token_bracket_curly_left() { + let input = r#" +object "Test" ( + code { + { + return(0, 0) + } + } + object "Test_deployed" { + code { + { + return(0, 0) + } + } + } +} + "#; + + let mut lexer = Lexer::new(input.to_owned()); + let result = Object::parse(&mut lexer, None); + assert_eq!( + result, + Err(Error::InvalidToken { + location: Location::new(2, 15), + expected: vec!["{"], + found: "(".to_owned(), + } + .into()) + ); + } + + #[test] + fn error_invalid_token_object_inner() { + let input = r#" +object "Test" { + code { + { + return(0, 0) + } + } + class "Test_deployed" { + code { + { + return(0, 0) + } + } + } +} + "#; + + let mut lexer = Lexer::new(input.to_owned()); + let result = Object::parse(&mut lexer, None); + assert_eq!( + result, + Err(Error::InvalidToken { + location: Location::new(8, 5), + expected: vec!["object", "}"], + found: "class".to_owned(), + } + .into()) + ); + } + + #[test] + fn error_invalid_object_name() { + let input = r#" +object "Test" { + code { + { + return(0, 0) + } + } + object "Invalid" { + code { + { + return(0, 0) + } + } + } +} + "#; + + let mut lexer = Lexer::new(input.to_owned()); + let result = Object::parse(&mut lexer, None); + assert_eq!( + result, + Err(Error::InvalidObjectName { + location: Location::new(8, 5), + expected: "Test_deployed".to_owned(), + found: "Invalid".to_owned(), + } + .into()) + ); + } +} diff --git a/crates/solidity/src/yul/parser/statement/switch/case.rs b/crates/solidity/src/yul/parser/statement/switch/case.rs new file mode 100644 index 0000000..f4812bf --- /dev/null +++ b/crates/solidity/src/yul/parser/statement/switch/case.rs @@ -0,0 +1,113 @@ +//! +//! The switch statement case. +//! + +use std::collections::HashSet; + +use serde::Deserialize; +use serde::Serialize; + +use crate::yul::error::Error; +use crate::yul::lexer::token::lexeme::Lexeme; +use crate::yul::lexer::token::location::Location; +use crate::yul::lexer::token::Token; +use crate::yul::lexer::Lexer; +use crate::yul::parser::error::Error as ParserError; +use crate::yul::parser::statement::block::Block; +use crate::yul::parser::statement::expression::literal::Literal; + +/// +/// The Yul switch statement case. +/// +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +pub struct Case { + /// The location. + pub location: Location, + /// The matched constant. + pub literal: Literal, + /// The case block. + pub block: Block, +} + +impl Case { + /// + /// The element parser. + /// + pub fn parse(lexer: &mut Lexer, initial: Option) -> Result { + let token = crate::yul::parser::take_or_next(initial, lexer)?; + + let (location, literal) = match token { + token @ Token { + lexeme: Lexeme::Literal(_), + location, + .. + } => (location, Literal::parse(lexer, Some(token))?), + token => { + return Err(ParserError::InvalidToken { + location: token.location, + expected: vec!["{literal}"], + found: token.lexeme.to_string(), + } + .into()); + } + }; + + let block = Block::parse(lexer, None)?; + + Ok(Self { + location, + literal, + block, + }) + } + + /// + /// Get the list of missing deployable libraries. + /// + pub fn get_missing_libraries(&self) -> HashSet { + self.block.get_missing_libraries() + } +} + +#[cfg(test)] +mod tests { + use crate::yul::lexer::token::location::Location; + use crate::yul::lexer::Lexer; + use crate::yul::parser::error::Error; + use crate::yul::parser::statement::object::Object; + + #[test] + fn error_invalid_token_literal() { + let input = r#" +object "Test" { + code { + { + return(0, 0) + } + } + object "Test_deployed" { + code { + { + switch 42 + case x {} + default {} + } + } + } + } +} + "#; + + let mut lexer = Lexer::new(input.to_owned()); + let result = Object::parse(&mut lexer, None); + assert_eq!( + result, + Err(Error::InvalidToken { + location: Location::new(12, 26), + expected: vec!["{literal}"], + found: "x".to_owned(), + } + .into()) + ); + } +} diff --git a/crates/solidity/src/yul/parser/statement/switch/mod.rs b/crates/solidity/src/yul/parser/statement/switch/mod.rs new file mode 100644 index 0000000..5328b0f --- /dev/null +++ b/crates/solidity/src/yul/parser/statement/switch/mod.rs @@ -0,0 +1,229 @@ +//! +//! The switch statement. +//! + +pub mod case; + +use std::collections::HashSet; + +use serde::Deserialize; +use serde::Serialize; + +use crate::yul::error::Error; +use crate::yul::lexer::token::lexeme::keyword::Keyword; +use crate::yul::lexer::token::lexeme::Lexeme; +use crate::yul::lexer::token::location::Location; +use crate::yul::lexer::token::Token; +use crate::yul::lexer::Lexer; +use crate::yul::parser::error::Error as ParserError; +use crate::yul::parser::statement::block::Block; +use crate::yul::parser::statement::expression::Expression; + +use self::case::Case; + +/// +/// The Yul switch statement. +/// +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +pub struct Switch { + /// The location. + pub location: Location, + /// The expression being matched. + pub expression: Expression, + /// The non-default cases. + pub cases: Vec, + /// The optional default case, if `cases` do not cover all possible values. + pub default: Option, +} + +/// +/// The parsing state. +/// +pub enum State { + /// After match expression. + CaseOrDefaultKeyword, + /// After `case`. + CaseBlock, + /// After `default`. + DefaultBlock, +} + +impl Switch { + /// + /// The element parser. + /// + pub fn parse(lexer: &mut Lexer, initial: Option) -> Result { + let mut token = crate::yul::parser::take_or_next(initial, lexer)?; + let location = token.location; + let mut state = State::CaseOrDefaultKeyword; + + let expression = Expression::parse(lexer, Some(token.clone()))?; + let mut cases = Vec::new(); + let mut default = None; + + loop { + match state { + State::CaseOrDefaultKeyword => match lexer.peek()? { + _token @ Token { + lexeme: Lexeme::Keyword(Keyword::Case), + .. + } => { + token = _token; + state = State::CaseBlock; + } + _token @ Token { + lexeme: Lexeme::Keyword(Keyword::Default), + .. + } => { + token = _token; + state = State::DefaultBlock; + } + _token => { + token = _token; + break; + } + }, + State::CaseBlock => { + lexer.next()?; + cases.push(Case::parse(lexer, None)?); + state = State::CaseOrDefaultKeyword; + } + State::DefaultBlock => { + lexer.next()?; + default = Some(Block::parse(lexer, None)?); + break; + } + } + } + + if cases.is_empty() && default.is_none() { + return Err(ParserError::InvalidToken { + location: token.location, + expected: vec!["case", "default"], + found: token.lexeme.to_string(), + } + .into()); + } + + Ok(Self { + location, + expression, + cases, + default, + }) + } + + /// + /// Get the list of missing deployable libraries. + /// + pub fn get_missing_libraries(&self) -> HashSet { + let mut libraries = HashSet::new(); + for case in self.cases.iter() { + libraries.extend(case.get_missing_libraries()); + } + if let Some(default) = &self.default { + libraries.extend(default.get_missing_libraries()); + } + libraries + } +} + +impl era_compiler_llvm_context::EraVMWriteLLVM for Switch +where + D: era_compiler_llvm_context::EraVMDependency + Clone, +{ + fn into_llvm( + self, + context: &mut era_compiler_llvm_context::EraVMContext, + ) -> anyhow::Result<()> { + let scrutinee = self.expression.into_llvm(context)?; + + if self.cases.is_empty() { + if let Some(block) = self.default { + block.into_llvm(context)?; + } + return Ok(()); + } + + let current_block = context.basic_block(); + let join_block = context.append_basic_block("switch_join_block"); + + let mut branches = Vec::with_capacity(self.cases.len()); + for (index, case) in self.cases.into_iter().enumerate() { + let constant = case.literal.into_llvm(context)?.to_llvm(); + + let expression_block = context + .append_basic_block(format!("switch_case_branch_{}_block", index + 1).as_str()); + context.set_basic_block(expression_block); + case.block.into_llvm(context)?; + context.build_unconditional_branch(join_block); + + branches.push((constant.into_int_value(), expression_block)); + } + + let default_block = match self.default { + Some(default) => { + let default_block = context.append_basic_block("switch_default_block"); + context.set_basic_block(default_block); + default.into_llvm(context)?; + context.build_unconditional_branch(join_block); + default_block + } + None => join_block, + }; + + context.set_basic_block(current_block); + context.builder().build_switch( + scrutinee.expect("Always exists").to_llvm().into_int_value(), + default_block, + branches.as_slice(), + )?; + + context.set_basic_block(join_block); + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use crate::yul::lexer::token::location::Location; + use crate::yul::lexer::Lexer; + use crate::yul::parser::error::Error; + use crate::yul::parser::statement::object::Object; + + #[test] + fn error_invalid_token_case() { + let input = r#" +object "Test" { + code { + { + return(0, 0) + } + } + object "Test_deployed" { + code { + { + switch 42 + branch x {} + default {} + } + } + } + } +} + "#; + + let mut lexer = Lexer::new(input.to_owned()); + let result = Object::parse(&mut lexer, None); + assert_eq!( + result, + Err(Error::InvalidToken { + location: Location::new(12, 21), + expected: vec!["case", "default"], + found: "branch".to_owned(), + } + .into()) + ); + } +} diff --git a/crates/solidity/src/yul/parser/statement/variable_declaration.rs b/crates/solidity/src/yul/parser/statement/variable_declaration.rs new file mode 100644 index 0000000..515e0e7 --- /dev/null +++ b/crates/solidity/src/yul/parser/statement/variable_declaration.rs @@ -0,0 +1,262 @@ +//! +//! The variable declaration statement. +//! + +use std::collections::HashSet; + +use inkwell::types::BasicType; +use inkwell::values::BasicValue; +use serde::Deserialize; +use serde::Serialize; + +use crate::yul::error::Error; +use crate::yul::lexer::token::lexeme::symbol::Symbol; +use crate::yul::lexer::token::lexeme::Lexeme; +use crate::yul::lexer::token::location::Location; +use crate::yul::lexer::token::Token; +use crate::yul::lexer::Lexer; +use crate::yul::parser::error::Error as ParserError; +use crate::yul::parser::identifier::Identifier; +use crate::yul::parser::statement::expression::function_call::name::Name as FunctionName; +use crate::yul::parser::statement::expression::Expression; + +/// +/// The Yul variable declaration statement. +/// +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +pub struct VariableDeclaration { + /// The location. + pub location: Location, + /// The variable bindings list. + pub bindings: Vec, + /// The variable initializing expression. + pub expression: Option, +} + +impl VariableDeclaration { + /// + /// The element parser. + /// + pub fn parse( + lexer: &mut Lexer, + initial: Option, + ) -> Result<(Self, Option), Error> { + let token = crate::yul::parser::take_or_next(initial, lexer)?; + let location = token.location; + + let (bindings, next) = Identifier::parse_typed_list(lexer, Some(token))?; + for binding in bindings.iter() { + match FunctionName::from(binding.inner.as_str()) { + FunctionName::UserDefined(_) => continue, + _function_name => { + return Err(ParserError::ReservedIdentifier { + location: binding.location, + identifier: binding.inner.to_owned(), + } + .into()) + } + } + } + + match crate::yul::parser::take_or_next(next, lexer)? { + Token { + lexeme: Lexeme::Symbol(Symbol::Assignment), + .. + } => {} + token => { + return Ok(( + Self { + location, + bindings, + expression: None, + }, + Some(token), + )) + } + } + + let expression = Expression::parse(lexer, None)?; + + Ok(( + Self { + location, + bindings, + expression: Some(expression), + }, + None, + )) + } + + /// + /// Get the list of missing deployable libraries. + /// + pub fn get_missing_libraries(&self) -> HashSet { + self.expression + .as_ref() + .map_or_else(HashSet::new, |expression| { + expression.get_missing_libraries() + }) + } +} + +impl era_compiler_llvm_context::EraVMWriteLLVM for VariableDeclaration +where + D: era_compiler_llvm_context::EraVMDependency + Clone, +{ + fn into_llvm<'ctx>( + mut self, + context: &mut era_compiler_llvm_context::EraVMContext<'ctx, D>, + ) -> anyhow::Result<()> { + if self.bindings.len() == 1 { + let identifier = self.bindings.remove(0); + let r#type = identifier.r#type.unwrap_or_default().into_llvm(context); + let pointer = context.build_alloca(r#type, identifier.inner.as_str()); + context + .current_function() + .borrow_mut() + .insert_stack_pointer(identifier.inner.clone(), pointer); + + let value = if let Some(expression) = self.expression { + match expression.into_llvm(context)? { + Some(mut value) => { + if let Some(constant) = value.constant.take() { + context + .current_function() + .borrow_mut() + .yul_mut() + .insert_constant(identifier.inner, constant); + } + + value.to_llvm() + } + None => r#type.const_zero().as_basic_value_enum(), + } + } else { + r#type.const_zero().as_basic_value_enum() + }; + context.build_store(pointer, value)?; + return Ok(()); + } + + for (index, binding) in self.bindings.iter().enumerate() { + let yul_type = binding + .r#type + .to_owned() + .unwrap_or_default() + .into_llvm(context); + let pointer = context.build_alloca( + yul_type.as_basic_type_enum(), + format!("binding_{index}_pointer").as_str(), + ); + context.build_store(pointer, yul_type.const_zero())?; + context + .current_function() + .borrow_mut() + .insert_stack_pointer(binding.inner.to_owned(), pointer); + } + + let expression = match self.expression.take() { + Some(expression) => expression, + None => return Ok(()), + }; + let location = expression.location(); + let expression = match expression.into_llvm(context)? { + Some(expression) => expression, + None => return Ok(()), + }; + + let llvm_type = context.structure_type( + self.bindings + .iter() + .map(|binding| { + binding + .r#type + .to_owned() + .unwrap_or_default() + .into_llvm(context) + .as_basic_type_enum() + }) + .collect::>>() + .as_slice(), + ); + if expression.value.get_type() != llvm_type.as_basic_type_enum() { + anyhow::bail!( + "{} Assignment to {:?} received an invalid number of arguments", + location, + self.bindings + ); + } + let pointer = context.build_alloca(llvm_type, "bindings_pointer"); + context.build_store(pointer, expression.to_llvm())?; + + for (index, binding) in self.bindings.into_iter().enumerate() { + let pointer = context.build_gep( + pointer, + &[ + context.field_const(0), + context + .integer_type(era_compiler_common::BIT_LENGTH_X32) + .const_int(index as u64, false), + ], + binding.r#type.unwrap_or_default().into_llvm(context), + format!("binding_{index}_gep_pointer").as_str(), + ); + + let value = context.build_load(pointer, format!("binding_{index}_value").as_str())?; + let pointer = context + .current_function() + .borrow_mut() + .get_stack_pointer(binding.inner.as_str()) + .ok_or_else(|| { + anyhow::anyhow!( + "{} Assignment to an undeclared variable `{}`", + binding.location, + binding.inner + ) + })?; + context.build_store(pointer, value)?; + } + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use crate::yul::lexer::token::location::Location; + use crate::yul::lexer::Lexer; + use crate::yul::parser::error::Error; + use crate::yul::parser::statement::object::Object; + + #[test] + fn error_reserved_identifier() { + let input = r#" +object "Test" { + code { + { + return(0, 0) + } + } + object "Test_deployed" { + code { + { + let basefee := 42 + return(0, 0) + } + } + } +} + "#; + + let mut lexer = Lexer::new(input.to_owned()); + let result = Object::parse(&mut lexer, None); + assert_eq!( + result, + Err(Error::ReservedIdentifier { + location: Location::new(11, 21), + identifier: "basefee".to_owned() + } + .into()) + ); + } +} diff --git a/crates/solidity/src/yul/parser/type.rs b/crates/solidity/src/yul/parser/type.rs new file mode 100644 index 0000000..a4f57f3 --- /dev/null +++ b/crates/solidity/src/yul/parser/type.rs @@ -0,0 +1,88 @@ +//! +//! The YUL source code type. +//! + +use serde::Deserialize; +use serde::Serialize; + +use crate::yul::error::Error; +use crate::yul::lexer::token::lexeme::keyword::Keyword; +use crate::yul::lexer::token::lexeme::Lexeme; +use crate::yul::lexer::token::Token; +use crate::yul::lexer::Lexer; +use crate::yul::parser::error::Error as ParserError; + +/// +/// The YUL source code type. +/// +/// The type is not currently in use, so all values have the `uint256` type by default. +/// +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +pub enum Type { + /// The `bool` type. + Bool, + /// The `int{N}` type. + Int(usize), + /// The `uint{N}` type. + UInt(usize), + /// The custom user-defined type. + Custom(String), +} + +impl Default for Type { + fn default() -> Self { + Self::UInt(era_compiler_common::BIT_LENGTH_FIELD) + } +} + +impl Type { + /// + /// The element parser. + /// + pub fn parse(lexer: &mut Lexer, initial: Option) -> Result { + let token = crate::yul::parser::take_or_next(initial, lexer)?; + + match token { + Token { + lexeme: Lexeme::Keyword(Keyword::Bool), + .. + } => Ok(Self::Bool), + Token { + lexeme: Lexeme::Keyword(Keyword::Int(bitlength)), + .. + } => Ok(Self::Int(bitlength)), + Token { + lexeme: Lexeme::Keyword(Keyword::Uint(bitlength)), + .. + } => Ok(Self::UInt(bitlength)), + Token { + lexeme: Lexeme::Identifier(identifier), + .. + } => Ok(Self::Custom(identifier.inner)), + token => Err(ParserError::InvalidToken { + location: token.location, + expected: vec!["{type}"], + found: token.lexeme.to_string(), + } + .into()), + } + } + + /// + /// Converts the type into its LLVM. + /// + pub fn into_llvm<'ctx, D>( + self, + context: &era_compiler_llvm_context::EraVMContext<'ctx, D>, + ) -> inkwell::types::IntType<'ctx> + where + D: era_compiler_llvm_context::EraVMDependency + Clone, + { + match self { + Self::Bool => context.integer_type(era_compiler_common::BIT_LENGTH_BOOLEAN), + Self::Int(bitlength) => context.integer_type(bitlength), + Self::UInt(bitlength) => context.integer_type(bitlength), + Self::Custom(_) => context.field_type(), + } + } +} diff --git a/crates/solidity/src/zksolc/arguments.rs b/crates/solidity/src/zksolc/arguments.rs new file mode 100644 index 0000000..701ff0c --- /dev/null +++ b/crates/solidity/src/zksolc/arguments.rs @@ -0,0 +1,393 @@ +//! +//! Solidity to EraVM compiler arguments. +//! + +use std::collections::BTreeSet; +use std::path::Path; +use std::path::PathBuf; + +use path_slash::PathExt; +use structopt::StructOpt; + +/// +/// Compiles the provided Solidity input files (or use the standard input if no files +/// are given or "-" is specified as a file name). Outputs the components based on the +/// chosen options, either to the standard output or to files within the designated +/// output directory. +/// +/// Example: zksolc ERC20.sol -O3 --bin --output-dir './build/' +/// +#[derive(Debug, StructOpt)] +#[structopt(name = "The EraVM Solidity compiler")] +pub struct Arguments { + /// Print the version and exit. + #[structopt(long = "version")] + pub version: bool, + + /// Specify the input paths and remappings. + /// If an argument contains a '=', it is considered a remapping. + /// Multiple Solidity files can be passed in the default Solidity mode. + /// Yul, LLVM IR, and EraVM Assembly modes currently support only a single file. + pub inputs: Vec, + + /// Set the given path as the root of the source tree instead of the root of the filesystem. + /// Passed to `solc` without changes. + #[structopt(long = "base-path")] + pub base_path: Option, + + /// Make an additional source directory available to the default import callback. + /// Can be used multiple times. Can only be used if the base path has a non-empty value. + /// Passed to `solc` without changes. + #[structopt(long = "include-path")] + pub include_paths: Vec, + + /// Allow a given path for imports. A list of paths can be supplied by separating them with a comma. + /// Passed to `solc` without changes. + #[structopt(long = "allow-paths")] + pub allow_paths: Option, + + /// Create one file per component and contract/file at the specified directory, if given. + #[structopt(short = "o", long = "output-dir")] + pub output_directory: Option, + + /// Overwrite existing files (used together with -o). + #[structopt(long = "overwrite")] + pub overwrite: bool, + + /// Set the optimization parameter -O[0 | 1 | 2 | 3 | s | z]. + /// Use `3` for best performance and `z` for minimal size. + #[structopt(short = "O", long = "optimization")] + pub optimization: Option, + + /// Try to recompile with -Oz if the bytecode is too large. + #[structopt(long = "fallback-Oz")] + pub fallback_to_optimizing_for_size: bool, + + /// Disable the system request memoization. + #[structopt(long = "disable-system-request-memoization")] + pub disable_system_request_memoization: bool, + + /// Disable the `solc` optimizer. + /// Use it if your project uses the `MSIZE` instruction, or in other cases. + /// Beware that it will prevent libraries from being inlined. + #[structopt(long = "disable-solc-optimizer")] + pub disable_solc_optimizer: bool, + + /// Specify the path to the `solc` executable. By default, the one in `${PATH}` is used. + /// Yul mode: `solc` is used for source code validation, as `zksolc` itself assumes that the input Yul is valid. + /// LLVM IR mode: `solc` is unused. + #[structopt(long = "solc")] + pub solc: Option, + + /// The EVM target version to generate IR for. + /// See https://github.com/matter-labs/era-compiler-common/blob/main/src/evm_version.rs for reference. + #[structopt(long = "evm-version")] + pub evm_version: Option, + + /// Specify addresses of deployable libraries. Syntax: `=
[, or whitespace] ...`. + /// Addresses are interpreted as hexadecimal strings prefixed with `0x`. + #[structopt(short = "l", long = "libraries")] + pub libraries: Vec, + + /// Output a single JSON document containing the specified information. + /// Available arguments: `abi`, `hashes`, `metadata`, `devdoc`, `userdoc`, `storage-layout`, `ast`, `asm`, `bin`, `bin-runtime`. + #[structopt(long = "combined-json")] + pub combined_json: Option, + + /// Switch to standard JSON input/output mode. Read from stdin, write the result to stdout. + /// This is the default used by the Hardhat plugin. + #[structopt(long = "standard-json")] + pub standard_json: bool, + + /// Switch to missing deployable libraries detection mode. + /// Only available for standard JSON input/output mode. + /// Contracts are not compiled in this mode, and all compilation artifacts are not included. + #[structopt(long = "detect-missing-libraries")] + pub detect_missing_libraries: bool, + + /// Switch to Yul mode. + /// Only one input Yul file is allowed. + /// Cannot be used with combined and standard JSON modes. + #[structopt(long = "yul")] + pub yul: bool, + + /// Switch to LLVM IR mode. + /// Only one input LLVM IR file is allowed. + /// Cannot be used with combined and standard JSON modes. + /// Use this mode at your own risk, as LLVM IR input validation is not implemented. + #[structopt(long = "llvm-ir")] + pub llvm_ir: bool, + + /// Switch to EraVM assembly mode. + /// Only one input EraVM assembly file is allowed. + /// Cannot be used with combined and standard JSON modes. + /// Use this mode at your own risk, as EraVM assembly input validation is not implemented. + #[structopt(long = "zkasm")] + pub zkasm: bool, + + /// Forcibly switch to EVM legacy assembly pipeline. + /// It is useful for older revisions of `solc` 0.8, where Yul was considered highly experimental + /// and contained more bugs than today. + #[structopt(long = "force-evmla")] + pub force_evmla: bool, + + /// Enable system contract compilation mode. + /// In this mode EraVM extensions are enabled. For example, calls to addresses `0xFFFF` and below + /// are substituted by special EraVM instructions. + /// In the Yul mode, the `verbatim_*` instruction family is available. + #[structopt(long = "system-mode")] + pub is_system_mode: bool, + + /// Set metadata hash mode. + /// The only supported value is `none` that disables appending the metadata hash. + /// Is enabled by default. + #[structopt(long = "metadata-hash")] + pub metadata_hash: Option, + + /// Output EraVM assembly of the contracts. + #[structopt(long = "asm")] + pub output_assembly: bool, + + /// Output EraVM bytecode of the contracts. + #[structopt(long = "bin")] + pub output_binary: bool, + + /// Suppress specified warnings. + /// Available arguments: `ecrecover`, `sendtransfer`, `extcodesize`, `txorigin`, `blocktimestamp`, `blocknumber`, `blockhash`. + #[structopt(long = "suppress-warnings")] + pub suppress_warnings: Option>, + + /// Dump all IRs to files in the specified directory. + /// Only for testing and debugging. + #[structopt(long = "debug-output-dir")] + pub debug_output_directory: Option, + + /// Set the verify-each option in LLVM. + /// Only for testing and debugging. + #[structopt(long = "llvm-verify-each")] + pub llvm_verify_each: bool, + + /// Set the debug-logging option in LLVM. + /// Only for testing and debugging. + #[structopt(long = "llvm-debug-logging")] + pub llvm_debug_logging: bool, + + /// Run this process recursively and provide JSON input to compile a single contract. + /// Only for usage from within the compiler. + #[structopt(long = "recursive-process")] + pub recursive_process: bool, +} + +impl Default for Arguments { + fn default() -> Self { + Self::new() + } +} + +impl Arguments { + /// + /// A shortcut constructor. + /// + pub fn new() -> Self { + Self::from_args() + } + + /// + /// Validates the arguments. + /// + #[allow(clippy::collapsible_if)] + pub fn validate(&self) -> anyhow::Result<()> { + if self.version && std::env::args().count() > 2 { + anyhow::bail!("No other options are allowed while getting the compiler version."); + } + + if self.recursive_process && std::env::args().count() > 2 { + anyhow::bail!("No other options are allowed in recursive mode."); + } + + let modes_count = [ + self.yul, + self.llvm_ir, + self.zkasm, + self.combined_json.is_some(), + self.standard_json, + ] + .iter() + .filter(|&&x| x) + .count(); + if modes_count > 1 { + anyhow::bail!("Only one modes is allowed at the same time: Yul, LLVM IR, EraVM assembly, combined JSON, standard JSON."); + } + + if self.yul || self.llvm_ir || self.zkasm { + if self.base_path.is_some() { + anyhow::bail!("`base-path` is not used in Yul, LLVM IR and EraVM assembly modes."); + } + if !self.include_paths.is_empty() { + anyhow::bail!( + "`include-paths` is not used in Yul, LLVM IR and EraVM assembly modes." + ); + } + if self.allow_paths.is_some() { + anyhow::bail!( + "`allow-paths` is not used in Yul, LLVM IR and EraVM assembly modes." + ); + } + if !self.libraries.is_empty() { + anyhow::bail!( + "Libraries are not supported in Yul, LLVM IR and EraVM assembly modes." + ); + } + + if self.evm_version.is_some() { + anyhow::bail!( + "`evm-version` is not used in Yul, LLVM IR and EraVM assembly modes." + ); + } + + if self.force_evmla { + anyhow::bail!("EVM legacy assembly mode is not supported in Yul, LLVM IR and EraVM assembly modes."); + } + + if self.disable_solc_optimizer { + anyhow::bail!("Disabling the solc optimizer is not supported in Yul, LLVM IR and EraVM assembly modes."); + } + } + + if self.llvm_ir || self.zkasm { + if self.solc.is_some() { + anyhow::bail!("`solc` is not used in LLVM IR and EraVM assembly modes."); + } + + if self.is_system_mode { + anyhow::bail!( + "System contract mode is not supported in LLVM IR and EraVM assembly modes." + ); + } + } + + if self.zkasm { + if self.optimization.is_some() { + anyhow::bail!("LLVM optimizations are not supported in EraVM assembly mode."); + } + + if self.fallback_to_optimizing_for_size { + anyhow::bail!("Falling back to -Oz is not supported in EraVM assembly mode."); + } + if self.disable_system_request_memoization { + anyhow::bail!("Disabling the system request memoization is not supported in EraVM assembly mode."); + } + } + + if self.combined_json.is_some() { + if self.output_assembly || self.output_binary { + anyhow::bail!( + "Cannot output assembly or binary outside of JSON in combined JSON mode." + ); + } + } + + if self.standard_json { + if self.output_assembly || self.output_binary { + anyhow::bail!( + "Cannot output assembly or binary outside of JSON in standard JSON mode." + ); + } + + if !self.inputs.is_empty() { + anyhow::bail!("Input files must be passed via standard JSON input."); + } + if !self.libraries.is_empty() { + anyhow::bail!("Libraries must be passed via standard JSON input."); + } + if self.evm_version.is_some() { + anyhow::bail!("EVM version must be passed via standard JSON input."); + } + + if self.output_directory.is_some() { + anyhow::bail!("Output directory cannot be used in standard JSON mode."); + } + if self.overwrite { + anyhow::bail!("Overwriting flag cannot be used in standard JSON mode."); + } + if self.disable_solc_optimizer { + anyhow::bail!( + "Disabling the solc optimizer must specified in standard JSON input settings." + ); + } + if self.optimization.is_some() { + anyhow::bail!("LLVM optimizations must specified in standard JSON input settings."); + } + if self.fallback_to_optimizing_for_size { + anyhow::bail!( + "Falling back to -Oz must specified in standard JSON input settings." + ); + } + if self.disable_system_request_memoization { + anyhow::bail!( + "Disabling the system request memoization must specified in standard JSON input settings." + ); + } + if self.metadata_hash.is_some() { + anyhow::bail!("Metadata hash mode must specified in standard JSON input settings."); + } + } + + Ok(()) + } + + /// + /// Returns remappings from input paths. + /// + pub fn split_input_files_and_remappings( + &self, + ) -> anyhow::Result<(Vec, Option>)> { + let mut input_files = Vec::with_capacity(self.inputs.len()); + let mut remappings = BTreeSet::new(); + + for input in self.inputs.iter() { + if input.contains('=') { + let mut parts = Vec::with_capacity(2); + for path in input.trim().split('=') { + let path = PathBuf::from(path); + parts.push( + Self::path_to_posix(path.as_path())? + .to_string_lossy() + .to_string(), + ); + } + if parts.len() != 2 { + anyhow::bail!( + "Invalid remapping `{}`: expected two parts separated by '='", + input + ); + } + remappings.insert(parts.join("=")); + } else { + let path = PathBuf::from(input.trim()); + let path = Self::path_to_posix(path.as_path())?; + input_files.push(path); + } + } + + let remappings = if remappings.is_empty() { + None + } else { + Some(remappings) + }; + + Ok((input_files, remappings)) + } + + /// + /// Normalizes an input path by converting it to POSIX format. + /// + fn path_to_posix(path: &Path) -> anyhow::Result { + let path = path + .to_slash() + .ok_or_else(|| anyhow::anyhow!("Input path {:?} POSIX conversion error", path))? + .to_string(); + let path = PathBuf::from(path.as_str()); + Ok(path) + } +} diff --git a/crates/solidity/src/zksolc/main.rs b/crates/solidity/src/zksolc/main.rs new file mode 100644 index 0000000..47aad84 --- /dev/null +++ b/crates/solidity/src/zksolc/main.rs @@ -0,0 +1,220 @@ +//! +//! Solidity to EraVM compiler binary. +//! + +pub mod arguments; + +use std::str::FromStr; + +use self::arguments::Arguments; + +/// The rayon worker stack size. +const RAYON_WORKER_STACK_SIZE: usize = 16 * 1024 * 1024; + +#[cfg(target_env = "musl")] +#[global_allocator] +static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; + +/// +/// The application entry point. +/// +fn main() { + std::process::exit(match main_inner() { + Ok(()) => era_compiler_common::EXIT_CODE_SUCCESS, + Err(error) => { + eprintln!("{error}"); + era_compiler_common::EXIT_CODE_FAILURE + } + }) +} + +/// +/// The auxiliary `main` function to facilitate the `?` error conversion operator. +/// +fn main_inner() -> anyhow::Result<()> { + let arguments = Arguments::new(); + arguments.validate()?; + + if arguments.version { + println!( + "{} v{} (LLVM build {:?})", + env!("CARGO_PKG_DESCRIPTION"), + env!("CARGO_PKG_VERSION"), + inkwell::support::get_llvm_version() + ); + return Ok(()); + } + + rayon::ThreadPoolBuilder::new() + .stack_size(RAYON_WORKER_STACK_SIZE) + .build_global() + .expect("Thread pool configuration failure"); + inkwell::support::enable_llvm_pretty_stack_trace(); + era_compiler_llvm_context::initialize_target(era_compiler_llvm_context::Target::PVM); // TODO: pass from CLI + + if arguments.recursive_process { + return revive_solidity::run_process(); + } + + let debug_config = match arguments.debug_output_directory { + Some(ref debug_output_directory) => { + std::fs::create_dir_all(debug_output_directory.as_path())?; + Some(era_compiler_llvm_context::DebugConfig::new( + debug_output_directory.to_owned(), + )) + } + None => None, + }; + + let (input_files, remappings) = arguments.split_input_files_and_remappings()?; + + let suppressed_warnings = match arguments.suppress_warnings { + Some(warnings) => Some(revive_solidity::Warning::try_from_strings( + warnings.as_slice(), + )?), + None => None, + }; + + let mut solc = revive_solidity::SolcCompiler::new( + arguments + .solc + .unwrap_or_else(|| revive_solidity::SolcCompiler::DEFAULT_EXECUTABLE_NAME.to_owned()), + )?; + + let evm_version = match arguments.evm_version { + Some(evm_version) => Some(era_compiler_common::EVMVersion::try_from( + evm_version.as_str(), + )?), + None => None, + }; + + let mut optimizer_settings = match arguments.optimization { + Some(mode) => era_compiler_llvm_context::OptimizerSettings::try_from_cli(mode)?, + None => era_compiler_llvm_context::OptimizerSettings::cycles(), + }; + if arguments.fallback_to_optimizing_for_size { + optimizer_settings.enable_fallback_to_size(); + } + if arguments.disable_system_request_memoization { + optimizer_settings.disable_system_request_memoization(); + } + optimizer_settings.is_verify_each_enabled = arguments.llvm_verify_each; + optimizer_settings.is_debug_logging_enabled = arguments.llvm_debug_logging; + + let include_metadata_hash = match arguments.metadata_hash { + Some(metadata_hash) => { + let metadata = + era_compiler_llvm_context::EraVMMetadataHash::from_str(metadata_hash.as_str())?; + metadata != era_compiler_llvm_context::EraVMMetadataHash::None + } + None => true, + }; + + let build = if arguments.yul { + revive_solidity::yul( + input_files.as_slice(), + &mut solc, + optimizer_settings, + arguments.is_system_mode, + include_metadata_hash, + debug_config, + ) + } else if arguments.llvm_ir { + revive_solidity::llvm_ir( + input_files.as_slice(), + optimizer_settings, + arguments.is_system_mode, + include_metadata_hash, + debug_config, + ) + } else if arguments.zkasm { + revive_solidity::zkasm(input_files.as_slice(), include_metadata_hash, debug_config) + } else if arguments.standard_json { + revive_solidity::standard_json( + &mut solc, + arguments.detect_missing_libraries, + arguments.force_evmla, + arguments.is_system_mode, + arguments.base_path, + arguments.include_paths, + arguments.allow_paths, + debug_config, + )?; + return Ok(()); + } else if let Some(format) = arguments.combined_json { + revive_solidity::combined_json( + format, + input_files.as_slice(), + arguments.libraries, + &mut solc, + evm_version, + !arguments.disable_solc_optimizer, + optimizer_settings, + arguments.force_evmla, + arguments.is_system_mode, + include_metadata_hash, + arguments.base_path, + arguments.include_paths, + arguments.allow_paths, + remappings, + suppressed_warnings, + debug_config, + arguments.output_directory, + arguments.overwrite, + )?; + return Ok(()); + } else { + revive_solidity::standard_output( + input_files.as_slice(), + arguments.libraries, + &mut solc, + evm_version, + !arguments.disable_solc_optimizer, + optimizer_settings, + arguments.force_evmla, + arguments.is_system_mode, + include_metadata_hash, + arguments.base_path, + arguments.include_paths, + arguments.allow_paths, + remappings, + suppressed_warnings, + debug_config, + ) + }?; + + if let Some(output_directory) = arguments.output_directory { + std::fs::create_dir_all(&output_directory)?; + + build.write_to_directory( + &output_directory, + arguments.output_assembly, + arguments.output_binary, + arguments.overwrite, + )?; + + eprintln!( + "Compiler run successful. Artifact(s) can be found in directory {output_directory:?}." + ); + } else if arguments.output_assembly || arguments.output_binary { + for (path, contract) in build.contracts.into_iter() { + if arguments.output_assembly { + println!( + "Contract `{}` assembly:\n\n{}", + path, contract.build.assembly_text + ); + } + if arguments.output_binary { + println!( + "Contract `{}` bytecode: 0x{}", + path, + hex::encode(contract.build.bytecode) + ); + } + } + } else { + eprintln!("Compiler run successful. No output requested. Use --asm and --bin flags."); + } + + Ok(()) +} diff --git a/crates/compilation-target/Cargo.toml b/crates/stdlib/Cargo.toml similarity index 52% rename from crates/compilation-target/Cargo.toml rename to crates/stdlib/Cargo.toml index cd0b35d..d41e6ee 100644 --- a/crates/compilation-target/Cargo.toml +++ b/crates/stdlib/Cargo.toml @@ -1,9 +1,9 @@ [package] -name = "revive-compilation-target" +name = "revive-stdlib" version = "0.1.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -inkwell = { workspace = true } +inkwell = { workspace = true, no-default-features = true, features = ["target-riscv", "no-libffi-linking", "llvm16-0"] } diff --git a/crates/stdlib/build.rs b/crates/stdlib/build.rs new file mode 100644 index 0000000..10f7513 --- /dev/null +++ b/crates/stdlib/build.rs @@ -0,0 +1,31 @@ +use std::{env, fs, path::Path, process::Command}; + +fn main() { + let lib = "stdlib.bc"; + let out_dir = env::var_os("OUT_DIR").expect("env should have $OUT_DIR"); + let bitcode_path = Path::new(&out_dir).join(lib); + + let output = Command::new("llvm-as") + .args([ + "-o", + bitcode_path.to_str().expect("$OUT_DIR should be UTF-8"), + "stdlib.ll", + ]) + .output() + .expect("should be able to invoke llvm-as"); + + assert!( + output.status.success(), + "failed to assemble the stdlib: {:?}", + output + ); + + let bitcode = fs::read(bitcode_path).expect("bitcode should have been built"); + let len = bitcode.len(); + let src_path = Path::new(&out_dir).join("stdlib.rs"); + let src = format!("pub static BITCODE: &[u8; {len}] = include_bytes!(\"{lib}\");"); + fs::write(src_path, src).expect("should be able to write in $OUT_DIR"); + + println!("cargo:rerun-if-changed=stdlib.ll"); + println!("cargo:rerun-if-changed=build.rs"); +} diff --git a/crates/stdlib/src/lib.rs b/crates/stdlib/src/lib.rs new file mode 100644 index 0000000..8a95a1b --- /dev/null +++ b/crates/stdlib/src/lib.rs @@ -0,0 +1,36 @@ +//! This crate vendors EVM related standard library functionality and provides a LLVM module, +//! exporting the standard library functions. +//! +//! The standard library code is inherited and adapted from [0]. +//! +//! [0]: [https://github.com/matter-labs/era-compiler-llvm/blob/v1.4.1/llvm/lib/Target/EraVM/eravm-stdlib.ll] +//! + +use inkwell::{context::Context, memory_buffer::MemoryBuffer, module::Module, support::LLVMString}; + +include!(concat!(env!("OUT_DIR"), "/stdlib.rs")); + +/// Creates a LLVM module from [BITCODE]. +/// +/// The module exports a bunch of EVM related "stdlib" functions. +/// +/// Returns `Error` if the bitcode fails to parse, which should never happen. +pub fn module<'context>( + context: &'context Context, + module_name: &str, +) -> Result, LLVMString> { + let buf = MemoryBuffer::create_from_memory_range(BITCODE, module_name); + Module::parse_bitcode_from_buffer(&buf, context) +} + +#[cfg(test)] +mod tests { + #[test] + fn it_works() { + inkwell::targets::Target::initialize_riscv(&Default::default()); + let context = inkwell::context::Context::create(); + let module = crate::module(&context, "stdlib").unwrap(); + + assert!(module.get_function("__signextend").is_some()); + } +} diff --git a/crates/stdlib/stdlib.ll b/crates/stdlib/stdlib.ll new file mode 100644 index 0000000..745c969 --- /dev/null +++ b/crates/stdlib/stdlib.ll @@ -0,0 +1,293 @@ +; Adapted from: https://github.com/matter-labs/era-compiler-llvm/blob/v1.4.0/llvm/lib/Target/EraVM/eravm-stdlib.ll + +target datalayout = "e-m:e-p:32:32-i64:64-n32-S128" +target triple = "riscv32-unknown-unknown-elf" + +define i256 @__addmod(i256 %arg1, i256 %arg2, i256 %modulo) #4 { +entry: + %is_zero = icmp eq i256 %modulo, 0 + br i1 %is_zero, label %return, label %addmod + +addmod: + %arg1.fr = freeze i256 %arg1 + %arg1m = urem i256 %arg1.fr, %modulo + %arg2m = urem i256 %arg2, %modulo + %sum = add i256 %arg1m, %arg2m + %obit = icmp ult i256 %sum, %arg1m + %sum.mod = urem i256 %sum, %modulo + br i1 %obit, label %overflow, label %return + +overflow: + %mod.inv = xor i256 %modulo, -1 + %sum1 = add i256 %sum, %mod.inv + %sum.ovf = add i256 %sum1, 1 + br label %return + +return: + %value = phi i256 [0, %entry], [%sum.mod, %addmod], [%sum.ovf, %overflow] + ret i256 %value +} + +define i256 @__clz(i256 %v) #0 { +entry: + %vs128 = lshr i256 %v, 128 + %vs128nz = icmp ne i256 %vs128, 0 + %n128 = select i1 %vs128nz, i256 128, i256 256 + %va128 = select i1 %vs128nz, i256 %vs128, i256 %v + %vs64 = lshr i256 %va128, 64 + %vs64nz = icmp ne i256 %vs64, 0 + %clza64 = sub i256 %n128, 64 + %n64 = select i1 %vs64nz, i256 %clza64, i256 %n128 + %va64 = select i1 %vs64nz, i256 %vs64, i256 %va128 + %vs32 = lshr i256 %va64, 32 + %vs32nz = icmp ne i256 %vs32, 0 + %clza32 = sub i256 %n64, 32 + %n32 = select i1 %vs32nz, i256 %clza32, i256 %n64 + %va32 = select i1 %vs32nz, i256 %vs32, i256 %va64 + %vs16 = lshr i256 %va32, 16 + %vs16nz = icmp ne i256 %vs16, 0 + %clza16 = sub i256 %n32, 16 + %n16 = select i1 %vs16nz, i256 %clza16, i256 %n32 + %va16 = select i1 %vs16nz, i256 %vs16, i256 %va32 + %vs8 = lshr i256 %va16, 8 + %vs8nz = icmp ne i256 %vs8, 0 + %clza8 = sub i256 %n16, 8 + %n8 = select i1 %vs8nz, i256 %clza8, i256 %n16 + %va8 = select i1 %vs8nz, i256 %vs8, i256 %va16 + %vs4 = lshr i256 %va8, 4 + %vs4nz = icmp ne i256 %vs4, 0 + %clza4 = sub i256 %n8, 4 + %n4 = select i1 %vs4nz, i256 %clza4, i256 %n8 + %va4 = select i1 %vs4nz, i256 %vs4, i256 %va8 + %vs2 = lshr i256 %va4, 2 + %vs2nz = icmp ne i256 %vs2, 0 + %clza2 = sub i256 %n4, 2 + %n2 = select i1 %vs2nz, i256 %clza2, i256 %n4 + %va2 = select i1 %vs2nz, i256 %vs2, i256 %va4 + %vs1 = lshr i256 %va2, 1 + %vs1nz = icmp ne i256 %vs1, 0 + %clza1 = sub i256 %n2, 2 + %clzax = sub i256 %n2, %va2 + %result = select i1 %vs1nz, i256 %clza1, i256 %clzax + ret i256 %result +} + +define i256 @__ulongrem(i256 %0, i256 %1, i256 %2) #0 { + %.not = icmp ult i256 %1, %2 + br i1 %.not, label %4, label %51 + +4: + %5 = tail call i256 @__clz(i256 %2) + %.not61 = icmp eq i256 %5, 0 + br i1 %.not61, label %13, label %6 + +6: + %7 = shl i256 %2, %5 + %8 = shl i256 %1, %5 + %9 = sub nuw nsw i256 256, %5 + %10 = lshr i256 %0, %9 + %11 = or i256 %10, %8 + %12 = shl i256 %0, %5 + br label %13 + +13: + %.054 = phi i256 [ %7, %6 ], [ %2, %4 ] + %.053 = phi i256 [ %11, %6 ], [ %1, %4 ] + %.052 = phi i256 [ %12, %6 ], [ %0, %4 ] + %14 = lshr i256 %.054, 128 + %15 = udiv i256 %.053, %14 + %16 = urem i256 %.053, %14 + %17 = and i256 %.054, 340282366920938463463374607431768211455 + %18 = lshr i256 %.052, 128 + br label %19 + +19: + %.056 = phi i256 [ %15, %13 ], [ %25, %.critedge ] + %.055 = phi i256 [ %16, %13 ], [ %26, %.critedge ] + %.not62 = icmp ult i256 %.056, 340282366920938463463374607431768211455 + br i1 %.not62, label %20, label %.critedge + +20: + %21 = mul nuw i256 %.056, %17 + %22 = shl nuw i256 %.055, 128 + %23 = or i256 %22, %18 + %24 = icmp ugt i256 %21, %23 + br i1 %24, label %.critedge, label %27 + +.critedge: + %25 = add i256 %.056, -1 + %26 = add i256 %.055, %14 + %.not65 = icmp ult i256 %26, 340282366920938463463374607431768211455 + br i1 %.not65, label %19, label %27 + +27: + %.157 = phi i256 [ %25, %.critedge ], [ %.056, %20 ] + %28 = shl i256 %.053, 128 + %29 = or i256 %18, %28 + %30 = and i256 %.157, 340282366920938463463374607431768211455 + %31 = mul i256 %30, %.054 + %32 = sub i256 %29, %31 + %33 = udiv i256 %32, %14 + %34 = urem i256 %32, %14 + %35 = and i256 %.052, 340282366920938463463374607431768211455 + br label %36 + +36: + %.2 = phi i256 [ %33, %27 ], [ %42, %.critedge1 ] + %.1 = phi i256 [ %34, %27 ], [ %43, %.critedge1 ] + %.not63 = icmp ult i256 %.2, 340282366920938463463374607431768211455 + br i1 %.not63, label %37, label %.critedge1 + +37: + %38 = mul nuw i256 %.2, %17 + %39 = shl i256 %.1, 128 + %40 = or i256 %39, %35 + %41 = icmp ugt i256 %38, %40 + br i1 %41, label %.critedge1, label %44 + +.critedge1: + %42 = add i256 %.2, -1 + %43 = add i256 %.1, %14 + %.not64 = icmp ult i256 %43, 340282366920938463463374607431768211455 + br i1 %.not64, label %36, label %44 + +44: + %.3 = phi i256 [ %42, %.critedge1 ], [ %.2, %37 ] + %45 = shl i256 %32, 128 + %46 = or i256 %45, %35 + %47 = and i256 %.3, 340282366920938463463374607431768211455 + %48 = mul i256 %47, %.054 + %49 = sub i256 %46, %48 + %50 = lshr i256 %49, %5 + br label %51 + +51: + %.0 = phi i256 [ %50, %44 ], [ -1, %3 ] + ret i256 %.0 +} + +define i256 @__mulmod(i256 %arg1, i256 %arg2, i256 %modulo) #0 { +entry: + %cccond = icmp eq i256 %modulo, 0 + br i1 %cccond, label %ccret, label %entrycont +ccret: + ret i256 0 +entrycont: + %arg1m = urem i256 %arg1, %modulo + %arg2m = urem i256 %arg2, %modulo + %less_then_2_128 = icmp ult i256 %modulo, 340282366920938463463374607431768211456 + br i1 %less_then_2_128, label %fast, label %slow +fast: + %prod = mul i256 %arg1m, %arg2m + %prodm = urem i256 %prod, %modulo + ret i256 %prodm +slow: + %arg1e = zext i256 %arg1m to i512 + %arg2e = zext i256 %arg2m to i512 + %prode = mul i512 %arg1e, %arg2e + %prodl = trunc i512 %prode to i256 + %prodeh = lshr i512 %prode, 256 + %prodh = trunc i512 %prodeh to i256 + %res = call i256 @__ulongrem(i256 %prodl, i256 %prodh, i256 %modulo) + ret i256 %res +} + +define i256 @__signextend(i256 %numbyte, i256 %value) #0 { +entry: + %is_overflow = icmp uge i256 %numbyte, 31 + br i1 %is_overflow, label %return, label %signextend + +signextend: + %numbit_byte = mul nuw nsw i256 %numbyte, 8 + %numbit = add nsw nuw i256 %numbit_byte, 7 + %numbit_inv = sub i256 256, %numbit + %signmask = shl i256 1, %numbit + %valmask = lshr i256 -1, %numbit_inv + %ext1 = shl i256 -1, %numbit + %signv = and i256 %signmask, %value + %sign = icmp ne i256 %signv, 0 + %valclean = and i256 %value, %valmask + %sext = select i1 %sign, i256 %ext1, i256 0 + %result = or i256 %sext, %valclean + br label %return + +return: + %signext_res = phi i256 [%value, %entry], [%result, %signextend] + ret i256 %signext_res +} + +define i256 @__exp(i256 %value, i256 %exp) "noinline-oz" #0 { +entry: + %exp_is_non_zero = icmp eq i256 %exp, 0 + br i1 %exp_is_non_zero, label %return, label %exponent_loop_body + +return: + %exp_res = phi i256 [ 1, %entry ], [ %exp_res.1, %exponent_loop_body ] + ret i256 %exp_res + +exponent_loop_body: + %exp_res.2 = phi i256 [ %exp_res.1, %exponent_loop_body ], [ 1, %entry ] + %exp_val = phi i256 [ %exp_val_halved, %exponent_loop_body ], [ %exp, %entry ] + %val_squared.1 = phi i256 [ %val_squared, %exponent_loop_body ], [ %value, %entry ] + %odd_test = and i256 %exp_val, 1 + %is_exp_odd = icmp eq i256 %odd_test, 0 + %exp_res.1.interm = select i1 %is_exp_odd, i256 1, i256 %val_squared.1 + %exp_res.1 = mul i256 %exp_res.1.interm, %exp_res.2 + %val_squared = mul i256 %val_squared.1, %val_squared.1 + %exp_val_halved = lshr i256 %exp_val, 1 + %exp_val_is_less_2 = icmp ult i256 %exp_val, 2 + br i1 %exp_val_is_less_2, label %return, label %exponent_loop_body +} + +define i256 @__exp_pow2(i256 %val_log2, i256 %exp) #0 { +entry: + %shift = mul nuw nsw i256 %val_log2, %exp + %is_overflow = icmp ugt i256 %shift, 255 + %shift_res = shl nuw i256 1, %shift + %res = select i1 %is_overflow, i256 0, i256 %shift_res + ret i256 %res +} + + +define i256 @__mod(i256 %arg1, i256 %arg2) #0 { +entry: + %is_divider_zero = icmp eq i256 %arg2, 0 + br i1 %is_divider_zero, label %return, label %remainder + +remainder: + %rem_res = urem i256 %arg1, %arg2 + br label %return + +return: + %res = phi i256 [ 0, %entry ], [ %rem_res, %remainder ] + ret i256 %res +} + +define i256 @__smod(i256 %arg1, i256 %arg2) #0 { +entry: + %is_divider_zero = icmp eq i256 %arg2, 0 + br i1 %is_divider_zero, label %return, label %division_overflow + +division_overflow: + %is_divided_int_min = icmp eq i256 %arg1, -57896044618658097711785492504343953926634992332820282019728792003956564819968 + %is_minus_one = icmp eq i256 %arg2, -1 + %is_overflow = and i1 %is_divided_int_min, %is_minus_one + br i1 %is_overflow, label %return, label %remainder + +remainder: + %rem_res = srem i256 %arg1, %arg2 + br label %return + +return: + %res = phi i256 [ 0, %entry ], [ 0, %division_overflow ], [ %rem_res, %remainder ] + ret i256 %res +} + + + +attributes #0 = { mustprogress nofree norecurse nosync nounwind readnone willreturn } +attributes #1 = { argmemonly readonly nofree null_pointer_is_valid } +attributes #2 = { argmemonly mustprogress nofree norecurse nosync nounwind willreturn null_pointer_is_valid } +attributes #3 = { noinline noreturn } +attributes #4 = { alwaysinline mustprogress nofree norecurse nosync nounwind readnone willreturn } +attributes #5 = { noreturn nounwind } \ No newline at end of file diff --git a/crates/target-polkavm/src/environment.rs b/crates/target-polkavm/src/environment.rs deleted file mode 100644 index 4ab479e..0000000 --- a/crates/target-polkavm/src/environment.rs +++ /dev/null @@ -1,77 +0,0 @@ -use inkwell::{builder::Builder, context::Context, module::Module, values::FunctionValue}; -use polkavm_common::elf::FnMetadata; - -use revive_compilation_target::environment::Environment; -use revive_compilation_target::target::Target; - -use crate::PolkaVm; - -impl<'ctx> Environment<'ctx> for PolkaVm { - fn call_start(&'ctx self, builder: &Builder<'ctx>, start: FunctionValue<'ctx>) -> Module<'ctx> { - let module = self.context().create_module("entrypoint"); - - let (call, deploy) = pvm_exports(&self.0); - module.link_in_module(call).unwrap(); - module.link_in_module(deploy).unwrap(); - - let function_type = self.context().void_type().fn_type(&[], false); - - let call = module.add_function("call", function_type, None); - call.set_section(Some(".text.polkavm_export")); - builder.position_at_end(self.context().append_basic_block(call, "entry")); - builder.build_call(start, &[], "call_start"); - builder.build_return(None); - - let deploy = module.add_function("deploy", function_type, None); - deploy.set_section(Some(".text.polkavm_export")); - builder.position_at_end(self.context().append_basic_block(deploy, "entry")); - builder.build_unreachable(); - builder.build_return(None); - - module - } -} - -pub(super) fn pvm_exports(context: &Context) -> (Module, Module) { - let call_m = context.create_module("pvm_call"); - let deploy_m = context.create_module("pvm_deploy"); - - call_m.set_inline_assembly(&generate_export_assembly("call")); - deploy_m.set_inline_assembly(&generate_export_assembly("deploy")); - - (call_m, deploy_m) -} - -fn generate_export_assembly(symbol: &str) -> String { - let mut assembly = String::new(); - - assembly.push_str(".pushsection .polkavm_exports,\"\",@progbits\n"); - assembly.push_str(".byte 1\n"); // Version. - assembly.push_str(&format!(".4byte {symbol}\n")); // Address - - // Metadata - let mut metadata = Vec::new(); - FnMetadata { - name: symbol.to_string(), - args: Default::default(), - return_ty: Default::default(), - } - .serialize(|slice| metadata.extend_from_slice(slice)); - - assembly.push_str(&bytes_to_asm(&metadata)); - - assembly.push_str(".popsection\n"); - - assembly -} - -pub fn bytes_to_asm(bytes: &[u8]) -> String { - use std::fmt::Write; - - let mut out = String::with_capacity(bytes.len() * 11); - for &byte in bytes { - writeln!(&mut out, ".byte 0x{:02x}", byte).unwrap(); - } - - out -} diff --git a/crates/target-polkavm/src/lib.rs b/crates/target-polkavm/src/lib.rs deleted file mode 100644 index 2765ea8..0000000 --- a/crates/target-polkavm/src/lib.rs +++ /dev/null @@ -1,13 +0,0 @@ -use inkwell::context::Context; - -pub mod environment; -pub mod linker; -pub mod target; - -pub struct PolkaVm(Context); - -impl Default for PolkaVm { - fn default() -> Self { - Self(Context::create()) - } -} diff --git a/crates/target-polkavm/src/target.rs b/crates/target-polkavm/src/target.rs deleted file mode 100644 index e070dc3..0000000 --- a/crates/target-polkavm/src/target.rs +++ /dev/null @@ -1,34 +0,0 @@ -use inkwell::{ - context::Context, memory_buffer::MemoryBuffer, module::Module, targets::RelocMode, - OptimizationLevel, -}; -use revive_compilation_target::target::Target; - -use crate::PolkaVm; - -impl<'ctx> Target<'ctx> for PolkaVm { - const TARGET_NAME: &'static str = "riscv32"; - const TARGET_TRIPLE: &'static str = "riscv32-unknown-unknown-elf"; - const TARGET_FEATURES: &'static str = "+e,+m"; - const CPU: &'static str = "generic-rv32"; - const RELOC_MODE: RelocMode = RelocMode::PIC; - - fn libraries(&'ctx self) -> Vec> { - let guest_bitcode = include_bytes!("../polkavm_guest.bc"); - let imports = MemoryBuffer::create_from_memory_range(guest_bitcode, "guest_bc"); - - vec![Module::parse_bitcode_from_buffer(&imports, &self.0).unwrap()] - } - - fn context(&self) -> &Context { - &self.0 - } - - fn link(&self, blob: &[u8]) -> Vec { - crate::linker::link(blob) - } - - fn optimization_level(&self) -> OptimizationLevel { - OptimizationLevel::Aggressive - } -} diff --git a/erc20_paris.yul b/erc20_paris.yul deleted file mode 100644 index 98afda8..0000000 --- a/erc20_paris.yul +++ /dev/null @@ -1,2672 +0,0 @@ -digraph { - 0 [ color=red shape=oval label="Start"] - 1 [ color=blue shape=diamond label="Dynamic jump table"] - 2 [ color=red shape=oval label="Terminator"] - 3 [ color=black shape=rectangle label="Bytecode range: (0x00, 0x08] ---- -PUSH([128]) -PUSH([64]) -MSTORE -CALLVALUE -DUP(1) -ISZERO -PUSH([0, 0, 17]) -JUMPI -"] - 4 [ color=black shape=rectangle label="Bytecode range: (0x08, 0x0b] ---- -PUSH([0]) -DUP(1) -REVERT -"] - 5 [ color=black shape=rectangle label="Bytecode range: (0x0b, 0x20] ---- -JUMPDEST -POP -PUSH([64]) -MLOAD -PUSH([0, 11, 163]) -CODESIZE -SUB -DUP(1) -PUSH([0, 11, 163]) -DUP(4) -CODECOPY -DUP(2) -ADD -PUSH([64]) -DUP(2) -SWAP(1) -MSTORE -PUSH([0, 0, 52]) -SWAP(2) -PUSH([0, 1, 34]) -JUMP -"] - 6 [ color=black shape=rectangle label="Bytecode range: (0x20, 0x27] ---- -JUMPDEST -PUSH([3]) -PUSH([0, 0, 66]) -DUP(4) -DUP(3) -PUSH([0, 2, 29]) -JUMP -"] - 7 [ color=black shape=rectangle label="Bytecode range: (0x27, 0x2f] ---- -JUMPDEST -POP -PUSH([4]) -PUSH([0, 0, 81]) -DUP(3) -DUP(3) -PUSH([0, 2, 29]) -JUMP -"] - 8 [ color=black shape=rectangle label="Bytecode range: (0x2f, 0x35] ---- -JUMPDEST -POP -POP -POP -PUSH([0, 2, 233]) -JUMP -"] - 9 [ color=black shape=rectangle label="Bytecode range: (0x35, 0x41] ---- -JUMPDEST -PUSH([78, 72, 123, 113]) -PUSH([224]) -SHL -PUSH([0]) -MSTORE -PUSH([65]) -PUSH([4]) -MSTORE -PUSH([36]) -PUSH([0]) -REVERT -"] - 10 [ color=black shape=rectangle label="Bytecode range: (0x41, 0x4a] ---- -JUMPDEST -PUSH([0]) -DUP(3) -PUSH([31]) -DUP(4) -ADD -SLT -PUSH([0, 0, 130]) -JUMPI -"] - 11 [ color=black shape=rectangle label="Bytecode range: (0x4a, 0x4d] ---- -PUSH([0]) -DUP(1) -REVERT -"] - 12 [ color=black shape=rectangle label="Bytecode range: (0x4d, 0x5b] ---- -JUMPDEST -DUP(2) -MLOAD -PUSH([1]) -PUSH([1]) -PUSH([64]) -SHL -SUB -DUP(1) -DUP(3) -GT -ISZERO -PUSH([0, 0, 159]) -JUMPI -"] - 13 [ color=black shape=rectangle label="Bytecode range: (0x5b, 0x5e] ---- -PUSH([0, 0, 159]) -PUSH([0, 0, 90]) -JUMP -"] - 14 [ color=black shape=rectangle label="Bytecode range: (0x5e, 0x79] ---- -JUMPDEST -PUSH([64]) -MLOAD -PUSH([31]) -DUP(4) -ADD -PUSH([31]) -NOT -SWAP(1) -DUP(2) -AND -PUSH([63]) -ADD -AND -DUP(2) -ADD -SWAP(1) -DUP(3) -DUP(3) -GT -DUP(2) -DUP(4) -LT -OR -ISZERO -PUSH([0, 0, 202]) -JUMPI -"] - 15 [ color=black shape=rectangle label="Bytecode range: (0x79, 0x7c] ---- -PUSH([0, 0, 202]) -PUSH([0, 0, 90]) -JUMP -"] - 16 [ color=black shape=rectangle label="Bytecode range: (0x7c, 0x90] ---- -JUMPDEST -DUP(2) -PUSH([64]) -MSTORE -DUP(4) -DUP(2) -MSTORE -PUSH([32]) -SWAP(3) -POP -DUP(7) -PUSH([32]) -DUP(6) -DUP(9) -ADD -ADD -GT -ISZERO -PUSH([0, 0, 232]) -JUMPI -"] - 17 [ color=black shape=rectangle label="Bytecode range: (0x90, 0x93] ---- -PUSH([0]) -DUP(1) -REVERT -"] - 18 [ color=black shape=rectangle label="Bytecode range: (0x93, 0x97] ---- -JUMPDEST -PUSH([0]) -SWAP(2) -POP -"] - 19 [ color=black shape=rectangle label="Bytecode range: (0x97, 0x9e] ---- -JUMPDEST -DUP(4) -DUP(3) -LT -ISZERO -PUSH([0, 1, 12]) -JUMPI -"] - 20 [ color=black shape=rectangle label="Bytecode range: (0x9e, 0xb0] ---- -DUP(6) -DUP(3) -ADD -DUP(4) -ADD -MLOAD -DUP(2) -DUP(4) -ADD -DUP(5) -ADD -MSTORE -SWAP(1) -DUP(3) -ADD -SWAP(1) -PUSH([0, 0, 237]) -JUMP -"] - 21 [ color=black shape=rectangle label="Bytecode range: (0xb0, 0xc4] ---- -JUMPDEST -PUSH([0]) -PUSH([32]) -DUP(6) -DUP(4) -ADD -ADD -MSTORE -DUP(1) -SWAP(5) -POP -POP -POP -POP -POP -SWAP(3) -SWAP(2) -POP -POP -JUMP -"] - 22 [ color=black shape=rectangle label="Bytecode range: (0xc4, 0xcf] ---- -JUMPDEST -PUSH([0]) -DUP(1) -PUSH([64]) -DUP(4) -DUP(6) -SUB -SLT -ISZERO -PUSH([0, 1, 54]) -JUMPI -"] - 23 [ color=black shape=rectangle label="Bytecode range: (0xcf, 0xd2] ---- -PUSH([0]) -DUP(1) -REVERT -"] - 24 [ color=black shape=rectangle label="Bytecode range: (0xd2, 0xe0] ---- -JUMPDEST -DUP(3) -MLOAD -PUSH([1]) -PUSH([1]) -PUSH([64]) -SHL -SUB -DUP(1) -DUP(3) -GT -ISZERO -PUSH([0, 1, 78]) -JUMPI -"] - 25 [ color=black shape=rectangle label="Bytecode range: (0xe0, 0xe3] ---- -PUSH([0]) -DUP(1) -REVERT -"] - 26 [ color=black shape=rectangle label="Bytecode range: (0xe3, 0xeb] ---- -JUMPDEST -PUSH([0, 1, 92]) -DUP(7) -DUP(4) -DUP(8) -ADD -PUSH([0, 0, 112]) -JUMP -"] - 27 [ color=black shape=rectangle label="Bytecode range: (0xeb, 0xfa] ---- -JUMPDEST -SWAP(4) -POP -PUSH([32]) -DUP(6) -ADD -MLOAD -SWAP(2) -POP -DUP(1) -DUP(3) -GT -ISZERO -PUSH([0, 1, 115]) -JUMPI -"] - 28 [ color=black shape=rectangle label="Bytecode range: (0xfa, 0xfd] ---- -PUSH([0]) -DUP(1) -REVERT -"] - 29 [ color=black shape=rectangle label="Bytecode range: (0xfd, 0x106] ---- -JUMPDEST -POP -PUSH([0, 1, 130]) -DUP(6) -DUP(3) -DUP(7) -ADD -PUSH([0, 0, 112]) -JUMP -"] - 30 [ color=black shape=rectangle label="Bytecode range: (0x106, 0x110] ---- -JUMPDEST -SWAP(2) -POP -POP -SWAP(3) -POP -SWAP(3) -SWAP(1) -POP -JUMP -"] - 31 [ color=black shape=rectangle label="Bytecode range: (0x110, 0x11b] ---- -JUMPDEST -PUSH([1]) -DUP(2) -DUP(2) -SHR -SWAP(1) -DUP(3) -AND -DUP(1) -PUSH([0, 1, 161]) -JUMPI -"] - 32 [ color=black shape=rectangle label="Bytecode range: (0x11b, 0x120] ---- -PUSH([127]) -DUP(3) -AND -SWAP(2) -POP -"] - 33 [ color=black shape=rectangle label="Bytecode range: (0x120, 0x128] ---- -JUMPDEST -PUSH([32]) -DUP(3) -LT -DUP(2) -SUB -PUSH([0, 1, 194]) -JUMPI -"] - 34 [ color=black shape=rectangle label="Bytecode range: (0x128, 0x133] ---- -PUSH([78, 72, 123, 113]) -PUSH([224]) -SHL -PUSH([0]) -MSTORE -PUSH([34]) -PUSH([4]) -MSTORE -PUSH([36]) -PUSH([0]) -REVERT -"] - 35 [ color=black shape=rectangle label="Bytecode range: (0x133, 0x139] ---- -JUMPDEST -POP -SWAP(2) -SWAP(1) -POP -JUMP -"] - 36 [ color=black shape=rectangle label="Bytecode range: (0x139, 0x140] ---- -JUMPDEST -PUSH([31]) -DUP(3) -GT -ISZERO -PUSH([0, 2, 24]) -JUMPI -"] - 37 [ color=black shape=rectangle label="Bytecode range: (0x140, 0x154] ---- -PUSH([0]) -DUP(2) -PUSH([0]) -MSTORE -PUSH([32]) -PUSH([0]) -KECCAK256 -PUSH([31]) -DUP(6) -ADD -PUSH([5]) -SHR -DUP(2) -ADD -PUSH([32]) -DUP(7) -LT -ISZERO -PUSH([0, 1, 243]) -JUMPI -"] - 38 [ color=black shape=rectangle label="Bytecode range: (0x154, 0x156] ---- -POP -DUP(1) -"] - 39 [ color=black shape=rectangle label="Bytecode range: (0x156, 0x160] ---- -JUMPDEST -PUSH([31]) -DUP(6) -ADD -PUSH([5]) -SHR -DUP(3) -ADD -SWAP(2) -POP -"] - 40 [ color=black shape=rectangle label="Bytecode range: (0x160, 0x167] ---- -JUMPDEST -DUP(2) -DUP(2) -LT -ISZERO -PUSH([0, 2, 20]) -JUMPI -"] - 41 [ color=black shape=rectangle label="Bytecode range: (0x167, 0x16e] ---- -DUP(3) -DUP(2) -SSTORE -PUSH([1]) -ADD -PUSH([0, 1, 255]) -JUMP -"] - 42 [ color=black shape=rectangle label="Bytecode range: (0x16e, 0x172] ---- -JUMPDEST -POP -POP -POP -"] - 43 [ color=black shape=rectangle label="Bytecode range: (0x172, 0x177] ---- -JUMPDEST -POP -POP -POP -JUMP -"] - 44 [ color=black shape=rectangle label="Bytecode range: (0x177, 0x184] ---- -JUMPDEST -DUP(2) -MLOAD -PUSH([1]) -PUSH([1]) -PUSH([64]) -SHL -SUB -DUP(2) -GT -ISZERO -PUSH([0, 2, 57]) -JUMPI -"] - 45 [ color=black shape=rectangle label="Bytecode range: (0x184, 0x187] ---- -PUSH([0, 2, 57]) -PUSH([0, 0, 90]) -JUMP -"] - 46 [ color=black shape=rectangle label="Bytecode range: (0x187, 0x18f] ---- -JUMPDEST -PUSH([0, 2, 81]) -DUP(2) -PUSH([0, 2, 74]) -DUP(5) -SLOAD -PUSH([0, 1, 140]) -JUMP -"] - 47 [ color=black shape=rectangle label="Bytecode range: (0x18f, 0x193] ---- -JUMPDEST -DUP(5) -PUSH([0, 1, 200]) -JUMP -"] - 48 [ color=black shape=rectangle label="Bytecode range: (0x193, 0x19e] ---- -JUMPDEST -PUSH([32]) -DUP(1) -PUSH([31]) -DUP(4) -GT -PUSH([1]) -DUP(2) -EQ -PUSH([0, 2, 137]) -JUMPI -"] - 49 [ color=black shape=rectangle label="Bytecode range: (0x19e, 0x1a3] ---- -PUSH([0]) -DUP(5) -ISZERO -PUSH([0, 2, 112]) -JUMPI -"] - 50 [ color=black shape=rectangle label="Bytecode range: (0x1a3, 0x1a8] ---- -POP -DUP(6) -DUP(4) -ADD -MLOAD -"] - 51 [ color=black shape=rectangle label="Bytecode range: (0x1a8, 0x1bb] ---- -JUMPDEST -PUSH([0]) -NOT -PUSH([3]) -DUP(7) -SWAP(1) -SHL -SHR -NOT -AND -PUSH([1]) -DUP(6) -SWAP(1) -SHL -OR -DUP(6) -SSTORE -PUSH([0, 2, 20]) -JUMP -"] - 52 [ color=black shape=rectangle label="Bytecode range: (0x1bb, 0x1c8] ---- -JUMPDEST -PUSH([0]) -DUP(6) -DUP(2) -MSTORE -PUSH([32]) -DUP(2) -KECCAK256 -PUSH([31]) -NOT -DUP(7) -AND -SWAP(2) -"] - 53 [ color=black shape=rectangle label="Bytecode range: (0x1c8, 0x1cf] ---- -JUMPDEST -DUP(3) -DUP(2) -LT -ISZERO -PUSH([0, 2, 186]) -JUMPI -"] - 54 [ color=black shape=rectangle label="Bytecode range: (0x1cf, 0x1e2] ---- -DUP(9) -DUP(7) -ADD -MLOAD -DUP(3) -SSTORE -SWAP(5) -DUP(5) -ADD -SWAP(5) -PUSH([1]) -SWAP(1) -SWAP(2) -ADD -SWAP(1) -DUP(5) -ADD -PUSH([0, 2, 153]) -JUMP -"] - 55 [ color=black shape=rectangle label="Bytecode range: (0x1e2, 0x1ea] ---- -JUMPDEST -POP -DUP(6) -DUP(3) -LT -ISZERO -PUSH([0, 2, 217]) -JUMPI -"] - 56 [ color=black shape=rectangle label="Bytecode range: (0x1ea, 0x1fb] ---- -DUP(8) -DUP(6) -ADD -MLOAD -PUSH([0]) -NOT -PUSH([3]) -DUP(9) -SWAP(1) -SHL -PUSH([248]) -AND -SHR -NOT -AND -DUP(2) -SSTORE -"] - 57 [ color=black shape=rectangle label="Bytecode range: (0x1fb, 0x20a] ---- -JUMPDEST -POP -POP -POP -POP -POP -PUSH([1]) -SWAP(1) -DUP(2) -SHL -ADD -SWAP(1) -SSTORE -POP -JUMP -"] - 58 [ color=black shape=rectangle label="Bytecode range: (0x20a, 0x212] ---- -JUMPDEST -PUSH([8, 170]) -DUP(1) -PUSH([0, 2, 249]) -PUSH([0]) -CODECOPY -PUSH([0]) -RETURN -"] - 62 [ color=black shape=rectangle label="Bytecode range: (0x21e, 0x225] ---- -JUMPDEST -POP -PUSH([4]) -CALLDATASIZE -LT -PUSH([0, 185]) -JUMPI -"] - 63 [ color=black shape=rectangle label="Bytecode range: (0x225, 0x22e] ---- -PUSH([0]) -CALLDATALOAD -PUSH([224]) -SHR -DUP(1) -PUSH([57, 80, 147, 81]) -GT -PUSH([0, 129]) -JUMPI -"] - 64 [ color=black shape=rectangle label="Bytecode range: (0x22e, 0x233] ---- -DUP(1) -PUSH([164, 87, 194, 215]) -GT -PUSH([0, 91]) -JUMPI -"] - 65 [ color=black shape=rectangle label="Bytecode range: (0x233, 0x238] ---- -DUP(1) -PUSH([164, 87, 194, 215]) -EQ -PUSH([1, 119]) -JUMPI -"] - 66 [ color=black shape=rectangle label="Bytecode range: (0x238, 0x23d] ---- -DUP(1) -PUSH([169, 5, 156, 187]) -EQ -PUSH([1, 138]) -JUMPI -"] - 67 [ color=black shape=rectangle label="Bytecode range: (0x23d, 0x242] ---- -DUP(1) -PUSH([221, 98, 237, 62]) -EQ -PUSH([1, 157]) -JUMPI -"] - 68 [ color=black shape=rectangle label="Bytecode range: (0x242, 0x245] ---- -PUSH([0]) -DUP(1) -REVERT -"] - 69 [ color=black shape=rectangle label="Bytecode range: (0x245, 0x24b] ---- -JUMPDEST -DUP(1) -PUSH([57, 80, 147, 81]) -EQ -PUSH([1, 51]) -JUMPI -"] - 70 [ color=black shape=rectangle label="Bytecode range: (0x24b, 0x250] ---- -DUP(1) -PUSH([112, 160, 130, 49]) -EQ -PUSH([1, 70]) -JUMPI -"] - 71 [ color=black shape=rectangle label="Bytecode range: (0x250, 0x255] ---- -DUP(1) -PUSH([149, 216, 155, 65]) -EQ -PUSH([1, 111]) -JUMPI -"] - 72 [ color=black shape=rectangle label="Bytecode range: (0x255, 0x258] ---- -PUSH([0]) -DUP(1) -REVERT -"] - 73 [ color=black shape=rectangle label="Bytecode range: (0x258, 0x25e] ---- -JUMPDEST -DUP(1) -PUSH([6, 253, 222, 3]) -EQ -PUSH([0, 190]) -JUMPI -"] - 74 [ color=black shape=rectangle label="Bytecode range: (0x25e, 0x263] ---- -DUP(1) -PUSH([9, 94, 167, 179]) -EQ -PUSH([0, 220]) -JUMPI -"] - 75 [ color=black shape=rectangle label="Bytecode range: (0x263, 0x268] ---- -DUP(1) -PUSH([24, 22, 13, 221]) -EQ -PUSH([0, 255]) -JUMPI -"] - 76 [ color=black shape=rectangle label="Bytecode range: (0x268, 0x26d] ---- -DUP(1) -PUSH([35, 184, 114, 221]) -EQ -PUSH([1, 17]) -JUMPI -"] - 77 [ color=black shape=rectangle label="Bytecode range: (0x26d, 0x272] ---- -DUP(1) -PUSH([49, 60, 229, 103]) -EQ -PUSH([1, 36]) -JUMPI -"] - 78 [ color=black shape=rectangle label="Bytecode range: (0x272, 0x276] ---- -JUMPDEST -PUSH([0]) -DUP(1) -REVERT -"] - 79 [ color=black shape=rectangle label="Bytecode range: (0x276, 0x27a] ---- -JUMPDEST -PUSH([0, 198]) -PUSH([1, 214]) -JUMP -"] - 80 [ color=black shape=rectangle label="Bytecode range: (0x27a, 0x282] ---- -JUMPDEST -PUSH([64]) -MLOAD -PUSH([0, 211]) -SWAP(2) -SWAP(1) -PUSH([6, 243]) -JUMP -"] - 81 [ color=black shape=rectangle label="Bytecode range: (0x282, 0x28a] ---- -JUMPDEST -PUSH([64]) -MLOAD -DUP(1) -SWAP(2) -SUB -SWAP(1) -RETURN -"] - 82 [ color=black shape=rectangle label="Bytecode range: (0x28a, 0x291] ---- -JUMPDEST -PUSH([0, 239]) -PUSH([0, 234]) -CALLDATASIZE -PUSH([4]) -PUSH([7, 94]) -JUMP -"] - 83 [ color=black shape=rectangle label="Bytecode range: (0x291, 0x294] ---- -JUMPDEST -PUSH([2, 104]) -JUMP -"] - 84 [ color=black shape=rectangle label="Bytecode range: (0x294, 0x2a0] ---- -JUMPDEST -PUSH([64]) -MLOAD -SWAP(1) -ISZERO -ISZERO -DUP(2) -MSTORE -PUSH([32]) -ADD -PUSH([0, 211]) -JUMP -"] - 85 [ color=black shape=rectangle label="Bytecode range: (0x2a0, 0x2a3] ---- -JUMPDEST -PUSH([2]) -SLOAD -"] - 86 [ color=black shape=rectangle label="Bytecode range: (0x2a3, 0x2ad] ---- -JUMPDEST -PUSH([64]) -MLOAD -SWAP(1) -DUP(2) -MSTORE -PUSH([32]) -ADD -PUSH([0, 211]) -JUMP -"] - 87 [ color=black shape=rectangle label="Bytecode range: (0x2ad, 0x2b4] ---- -JUMPDEST -PUSH([0, 239]) -PUSH([1, 31]) -CALLDATASIZE -PUSH([4]) -PUSH([7, 136]) -JUMP -"] - 88 [ color=black shape=rectangle label="Bytecode range: (0x2b4, 0x2b7] ---- -JUMPDEST -PUSH([2, 130]) -JUMP -"] - 89 [ color=black shape=rectangle label="Bytecode range: (0x2b7, 0x2c1] ---- -JUMPDEST -PUSH([64]) -MLOAD -PUSH([18]) -DUP(2) -MSTORE -PUSH([32]) -ADD -PUSH([0, 211]) -JUMP -"] - 90 [ color=black shape=rectangle label="Bytecode range: (0x2c1, 0x2c8] ---- -JUMPDEST -PUSH([0, 239]) -PUSH([1, 65]) -CALLDATASIZE -PUSH([4]) -PUSH([7, 94]) -JUMP -"] - 91 [ color=black shape=rectangle label="Bytecode range: (0x2c8, 0x2cb] ---- -JUMPDEST -PUSH([2, 166]) -JUMP -"] - 92 [ color=black shape=rectangle label="Bytecode range: (0x2cb, 0x2d2] ---- -JUMPDEST -PUSH([1, 3]) -PUSH([1, 84]) -CALLDATASIZE -PUSH([4]) -PUSH([7, 196]) -JUMP -"] - 93 [ color=black shape=rectangle label="Bytecode range: (0x2d2, 0x2e7] ---- -JUMPDEST -PUSH([1]) -PUSH([1]) -PUSH([160]) -SHL -SUB -AND -PUSH([0]) -SWAP(1) -DUP(2) -MSTORE -PUSH([32]) -DUP(2) -SWAP(1) -MSTORE -PUSH([64]) -SWAP(1) -KECCAK256 -SLOAD -SWAP(1) -JUMP -"] - 94 [ color=black shape=rectangle label="Bytecode range: (0x2e7, 0x2eb] ---- -JUMPDEST -PUSH([0, 198]) -PUSH([2, 229]) -JUMP -"] - 95 [ color=black shape=rectangle label="Bytecode range: (0x2eb, 0x2f2] ---- -JUMPDEST -PUSH([0, 239]) -PUSH([1, 133]) -CALLDATASIZE -PUSH([4]) -PUSH([7, 94]) -JUMP -"] - 96 [ color=black shape=rectangle label="Bytecode range: (0x2f2, 0x2f5] ---- -JUMPDEST -PUSH([2, 244]) -JUMP -"] - 97 [ color=black shape=rectangle label="Bytecode range: (0x2f5, 0x2fc] ---- -JUMPDEST -PUSH([0, 239]) -PUSH([1, 152]) -CALLDATASIZE -PUSH([4]) -PUSH([7, 94]) -JUMP -"] - 98 [ color=black shape=rectangle label="Bytecode range: (0x2fc, 0x2ff] ---- -JUMPDEST -PUSH([3, 139]) -JUMP -"] - 99 [ color=black shape=rectangle label="Bytecode range: (0x2ff, 0x306] ---- -JUMPDEST -PUSH([1, 3]) -PUSH([1, 171]) -CALLDATASIZE -PUSH([4]) -PUSH([7, 230]) -JUMP -"] - 100 [ color=black shape=rectangle label="Bytecode range: (0x306, 0x32a] ---- -JUMPDEST -PUSH([1]) -PUSH([1]) -PUSH([160]) -SHL -SUB -SWAP(2) -DUP(3) -AND -PUSH([0]) -SWAP(1) -DUP(2) -MSTORE -PUSH([1]) -PUSH([32]) -SWAP(1) -DUP(2) -MSTORE -PUSH([64]) -DUP(1) -DUP(4) -KECCAK256 -SWAP(4) -SWAP(1) -SWAP(5) -AND -DUP(3) -MSTORE -SWAP(2) -SWAP(1) -SWAP(2) -MSTORE -KECCAK256 -SLOAD -SWAP(1) -JUMP -"] - 101 [ color=black shape=rectangle label="Bytecode range: (0x32a, 0x333] ---- -JUMPDEST -PUSH([96]) -PUSH([3]) -DUP(1) -SLOAD -PUSH([1, 229]) -SWAP(1) -PUSH([8, 25]) -JUMP -"] - 102 [ color=black shape=rectangle label="Bytecode range: (0x333, 0x355] ---- -JUMPDEST -DUP(1) -PUSH([31]) -ADD -PUSH([32]) -DUP(1) -SWAP(2) -DIV -MUL -PUSH([32]) -ADD -PUSH([64]) -MLOAD -SWAP(1) -DUP(2) -ADD -PUSH([64]) -MSTORE -DUP(1) -SWAP(3) -SWAP(2) -SWAP(1) -DUP(2) -DUP(2) -MSTORE -PUSH([32]) -ADD -DUP(3) -DUP(1) -SLOAD -PUSH([2, 17]) -SWAP(1) -PUSH([8, 25]) -JUMP -"] - 103 [ color=black shape=rectangle label="Bytecode range: (0x355, 0x35a] ---- -JUMPDEST -DUP(1) -ISZERO -PUSH([2, 94]) -JUMPI -"] - 104 [ color=black shape=rectangle label="Bytecode range: (0x35a, 0x35f] ---- -DUP(1) -PUSH([31]) -LT -PUSH([2, 51]) -JUMPI -"] - 105 [ color=black shape=rectangle label="Bytecode range: (0x35f, 0x36d] ---- -PUSH([1, 0]) -DUP(1) -DUP(4) -SLOAD -DIV -MUL -DUP(4) -MSTORE -SWAP(2) -PUSH([32]) -ADD -SWAP(2) -PUSH([2, 94]) -JUMP -"] - 106 [ color=black shape=rectangle label="Bytecode range: (0x36d, 0x378] ---- -JUMPDEST -DUP(3) -ADD -SWAP(2) -SWAP(1) -PUSH([0]) -MSTORE -PUSH([32]) -PUSH([0]) -KECCAK256 -SWAP(1) -"] - 107 [ color=black shape=rectangle label="Bytecode range: (0x378, 0x388] ---- -JUMPDEST -DUP(2) -SLOAD -DUP(2) -MSTORE -SWAP(1) -PUSH([1]) -ADD -SWAP(1) -PUSH([32]) -ADD -DUP(1) -DUP(4) -GT -PUSH([2, 65]) -JUMPI -"] - 108 [ color=black shape=rectangle label="Bytecode range: (0x388, 0x390] ---- -DUP(3) -SWAP(1) -SUB -PUSH([31]) -AND -DUP(3) -ADD -SWAP(2) -"] - 109 [ color=black shape=rectangle label="Bytecode range: (0x390, 0x39a] ---- -JUMPDEST -POP -POP -POP -POP -POP -SWAP(1) -POP -SWAP(1) -JUMP -"] - 110 [ color=black shape=rectangle label="Bytecode range: (0x39a, 0x3a3] ---- -JUMPDEST -PUSH([0]) -CALLER -PUSH([2, 118]) -DUP(2) -DUP(6) -DUP(6) -PUSH([3, 153]) -JUMP -"] - 111 [ color=black shape=rectangle label="Bytecode range: (0x3a3, 0x3a8] ---- -JUMPDEST -PUSH([1]) -SWAP(2) -POP -POP -"] - 112 [ color=black shape=rectangle label="Bytecode range: (0x3a8, 0x3ae] ---- -JUMPDEST -SWAP(3) -SWAP(2) -POP -POP -JUMP -"] - 113 [ color=black shape=rectangle label="Bytecode range: (0x3ae, 0x3b7] ---- -JUMPDEST -PUSH([0]) -CALLER -PUSH([2, 144]) -DUP(6) -DUP(3) -DUP(6) -PUSH([4, 189]) -JUMP -"] - 114 [ color=black shape=rectangle label="Bytecode range: (0x3b7, 0x3be] ---- -JUMPDEST -PUSH([2, 155]) -DUP(6) -DUP(6) -DUP(6) -PUSH([5, 79]) -JUMP -"] - 115 [ color=black shape=rectangle label="Bytecode range: (0x3be, 0x3c8] ---- -JUMPDEST -POP -PUSH([1]) -SWAP(5) -SWAP(4) -POP -POP -POP -POP -JUMP -"] - 116 [ color=black shape=rectangle label="Bytecode range: (0x3c8, 0x3f5] ---- -JUMPDEST -CALLER -PUSH([0]) -DUP(2) -DUP(2) -MSTORE -PUSH([1]) -PUSH([32]) -SWAP(1) -DUP(2) -MSTORE -PUSH([64]) -DUP(1) -DUP(4) -KECCAK256 -PUSH([1]) -PUSH([1]) -PUSH([160]) -SHL -SUB -DUP(8) -AND -DUP(5) -MSTORE -SWAP(1) -SWAP(2) -MSTORE -DUP(2) -KECCAK256 -SLOAD -SWAP(1) -SWAP(2) -SWAP(1) -PUSH([2, 118]) -SWAP(1) -DUP(3) -SWAP(1) -DUP(7) -SWAP(1) -PUSH([2, 224]) -SWAP(1) -DUP(8) -SWAP(1) -PUSH([8, 83]) -JUMP -"] - 117 [ color=black shape=rectangle label="Bytecode range: (0x3f5, 0x3f8] ---- -JUMPDEST -PUSH([3, 153]) -JUMP -"] - 118 [ color=black shape=rectangle label="Bytecode range: (0x3f8, 0x401] ---- -JUMPDEST -PUSH([96]) -PUSH([4]) -DUP(1) -SLOAD -PUSH([1, 229]) -SWAP(1) -PUSH([8, 25]) -JUMP -"] - 119 [ color=black shape=rectangle label="Bytecode range: (0x401, 0x428] ---- -JUMPDEST -CALLER -PUSH([0]) -DUP(2) -DUP(2) -MSTORE -PUSH([1]) -PUSH([32]) -SWAP(1) -DUP(2) -MSTORE -PUSH([64]) -DUP(1) -DUP(4) -KECCAK256 -PUSH([1]) -PUSH([1]) -PUSH([160]) -SHL -SUB -DUP(8) -AND -DUP(5) -MSTORE -SWAP(1) -SWAP(2) -MSTORE -DUP(2) -KECCAK256 -SLOAD -SWAP(1) -SWAP(2) -SWAP(1) -DUP(4) -DUP(2) -LT -ISZERO -PUSH([3, 126]) -JUMPI -"] - 120 [ color=black shape=rectangle label="Bytecode range: (0x428, 0x447] ---- -PUSH([64]) -MLOAD -PUSH([70, 27, 205]) -PUSH([229]) -SHL -DUP(2) -MSTORE -PUSH([32]) -PUSH([4]) -DUP(3) -ADD -MSTORE -PUSH([37]) -PUSH([36]) -DUP(3) -ADD -MSTORE -PUSH([69, 82, 67, 50, 48, 58, 32, 100, 101, 99, 114, 101, 97, 115, 101, 100, 32, 97, 108, 108, 111, 119, 97, 110, 99, 101, 32, 98, 101, 108, 111, 119]) -PUSH([68]) -DUP(3) -ADD -MSTORE -PUSH([32, 122, 101, 114, 111]) -PUSH([216]) -SHL -PUSH([100]) -DUP(3) -ADD -MSTORE -PUSH([132]) -ADD -"] - 121 [ color=black shape=rectangle label="Bytecode range: (0x447, 0x44f] ---- -JUMPDEST -PUSH([64]) -MLOAD -DUP(1) -SWAP(2) -SUB -SWAP(1) -REVERT -"] - 122 [ color=black shape=rectangle label="Bytecode range: (0x44f, 0x458] ---- -JUMPDEST -PUSH([2, 155]) -DUP(3) -DUP(7) -DUP(7) -DUP(5) -SUB -PUSH([3, 153]) -JUMP -"] - 123 [ color=black shape=rectangle label="Bytecode range: (0x458, 0x461] ---- -JUMPDEST -PUSH([0]) -CALLER -PUSH([2, 118]) -DUP(2) -DUP(6) -DUP(6) -PUSH([5, 79]) -JUMP -"] - 124 [ color=black shape=rectangle label="Bytecode range: (0x461, 0x46b] ---- -JUMPDEST -PUSH([1]) -PUSH([1]) -PUSH([160]) -SHL -SUB -DUP(4) -AND -PUSH([3, 251]) -JUMPI -"] - 125 [ color=black shape=rectangle label="Bytecode range: (0x46b, 0x48c] ---- -PUSH([64]) -MLOAD -PUSH([70, 27, 205]) -PUSH([229]) -SHL -DUP(2) -MSTORE -PUSH([32]) -PUSH([4]) -DUP(3) -ADD -MSTORE -PUSH([36]) -DUP(1) -DUP(3) -ADD -MSTORE -PUSH([69, 82, 67, 50, 48, 58, 32, 97, 112, 112, 114, 111, 118, 101, 32, 102, 114, 111, 109, 32, 116, 104, 101, 32, 122, 101, 114, 111, 32, 97, 100, 100]) -PUSH([68]) -DUP(3) -ADD -MSTORE -PUSH([114, 101, 115, 115]) -PUSH([224]) -SHL -PUSH([100]) -DUP(3) -ADD -MSTORE -PUSH([132]) -ADD -PUSH([3, 117]) -JUMP -"] - 126 [ color=black shape=rectangle label="Bytecode range: (0x48c, 0x496] ---- -JUMPDEST -PUSH([1]) -PUSH([1]) -PUSH([160]) -SHL -SUB -DUP(3) -AND -PUSH([4, 92]) -JUMPI -"] - 127 [ color=black shape=rectangle label="Bytecode range: (0x496, 0x4b7] ---- -PUSH([64]) -MLOAD -PUSH([70, 27, 205]) -PUSH([229]) -SHL -DUP(2) -MSTORE -PUSH([32]) -PUSH([4]) -DUP(3) -ADD -MSTORE -PUSH([34]) -PUSH([36]) -DUP(3) -ADD -MSTORE -PUSH([69, 82, 67, 50, 48, 58, 32, 97, 112, 112, 114, 111, 118, 101, 32, 116, 111, 32, 116, 104, 101, 32, 122, 101, 114, 111, 32, 97, 100, 100, 114, 101]) -PUSH([68]) -DUP(3) -ADD -MSTORE -PUSH([115, 115]) -PUSH([240]) -SHL -PUSH([100]) -DUP(3) -ADD -MSTORE -PUSH([132]) -ADD -PUSH([3, 117]) -JUMP -"] - 128 [ color=black shape=rectangle label="Bytecode range: (0x4b7, 0x4f0] ---- -JUMPDEST -PUSH([1]) -PUSH([1]) -PUSH([160]) -SHL -SUB -DUP(4) -DUP(2) -AND -PUSH([0]) -DUP(2) -DUP(2) -MSTORE -PUSH([1]) -PUSH([32]) -SWAP(1) -DUP(2) -MSTORE -PUSH([64]) -DUP(1) -DUP(4) -KECCAK256 -SWAP(5) -DUP(8) -AND -DUP(1) -DUP(5) -MSTORE -SWAP(5) -DUP(3) -MSTORE -SWAP(2) -DUP(3) -SWAP(1) -KECCAK256 -DUP(6) -SWAP(1) -SSTORE -SWAP(1) -MLOAD -DUP(5) -DUP(2) -MSTORE -PUSH([140, 91, 225, 229, 235, 236, 125, 91, 209, 79, 113, 66, 125, 30, 132, 243, 221, 3, 20, 192, 247, 178, 41, 30, 91, 32, 10, 200, 199, 195, 185, 37]) -SWAP(2) -ADD -PUSH([64]) -MLOAD -DUP(1) -SWAP(2) -SUB -SWAP(1) -LOG(3) -POP -POP -POP -JUMP -"] - 129 [ color=black shape=rectangle label="Bytecode range: (0x4f0, 0x516] ---- -JUMPDEST -PUSH([1]) -PUSH([1]) -PUSH([160]) -SHL -SUB -DUP(4) -DUP(2) -AND -PUSH([0]) -SWAP(1) -DUP(2) -MSTORE -PUSH([1]) -PUSH([32]) -SWAP(1) -DUP(2) -MSTORE -PUSH([64]) -DUP(1) -DUP(4) -KECCAK256 -SWAP(4) -DUP(7) -AND -DUP(4) -MSTORE -SWAP(3) -SWAP(1) -MSTORE -KECCAK256 -SLOAD -PUSH([0]) -NOT -DUP(2) -EQ -PUSH([5, 73]) -JUMPI -"] - 130 [ color=black shape=rectangle label="Bytecode range: (0x516, 0x51c] ---- -DUP(2) -DUP(2) -LT -ISZERO -PUSH([5, 60]) -JUMPI -"] - 131 [ color=black shape=rectangle label="Bytecode range: (0x51c, 0x536] ---- -PUSH([64]) -MLOAD -PUSH([70, 27, 205]) -PUSH([229]) -SHL -DUP(2) -MSTORE -PUSH([32]) -PUSH([4]) -DUP(3) -ADD -MSTORE -PUSH([29]) -PUSH([36]) -DUP(3) -ADD -MSTORE -PUSH([69, 82, 67, 50, 48, 58, 32, 105, 110, 115, 117, 102, 102, 105, 99, 105, 101, 110, 116, 32, 97, 108, 108, 111, 119, 97, 110, 99, 101, 0, 0, 0]) -PUSH([68]) -DUP(3) -ADD -MSTORE -PUSH([100]) -ADD -PUSH([3, 117]) -JUMP -"] - 132 [ color=black shape=rectangle label="Bytecode range: (0x536, 0x53f] ---- -JUMPDEST -PUSH([5, 73]) -DUP(5) -DUP(5) -DUP(5) -DUP(5) -SUB -PUSH([3, 153]) -JUMP -"] - 133 [ color=black shape=rectangle label="Bytecode range: (0x53f, 0x545] ---- -JUMPDEST -POP -POP -POP -POP -JUMP -"] - 134 [ color=black shape=rectangle label="Bytecode range: (0x545, 0x54f] ---- -JUMPDEST -PUSH([1]) -PUSH([1]) -PUSH([160]) -SHL -SUB -DUP(4) -AND -PUSH([5, 179]) -JUMPI -"] - 135 [ color=black shape=rectangle label="Bytecode range: (0x54f, 0x570] ---- -PUSH([64]) -MLOAD -PUSH([70, 27, 205]) -PUSH([229]) -SHL -DUP(2) -MSTORE -PUSH([32]) -PUSH([4]) -DUP(3) -ADD -MSTORE -PUSH([37]) -PUSH([36]) -DUP(3) -ADD -MSTORE -PUSH([69, 82, 67, 50, 48, 58, 32, 116, 114, 97, 110, 115, 102, 101, 114, 32, 102, 114, 111, 109, 32, 116, 104, 101, 32, 122, 101, 114, 111, 32, 97, 100]) -PUSH([68]) -DUP(3) -ADD -MSTORE -PUSH([100, 114, 101, 115, 115]) -PUSH([216]) -SHL -PUSH([100]) -DUP(3) -ADD -MSTORE -PUSH([132]) -ADD -PUSH([3, 117]) -JUMP -"] - 136 [ color=black shape=rectangle label="Bytecode range: (0x570, 0x57a] ---- -JUMPDEST -PUSH([1]) -PUSH([1]) -PUSH([160]) -SHL -SUB -DUP(3) -AND -PUSH([6, 21]) -JUMPI -"] - 137 [ color=black shape=rectangle label="Bytecode range: (0x57a, 0x59b] ---- -PUSH([64]) -MLOAD -PUSH([70, 27, 205]) -PUSH([229]) -SHL -DUP(2) -MSTORE -PUSH([32]) -PUSH([4]) -DUP(3) -ADD -MSTORE -PUSH([35]) -PUSH([36]) -DUP(3) -ADD -MSTORE -PUSH([69, 82, 67, 50, 48, 58, 32, 116, 114, 97, 110, 115, 102, 101, 114, 32, 116, 111, 32, 116, 104, 101, 32, 122, 101, 114, 111, 32, 97, 100, 100, 114]) -PUSH([68]) -DUP(3) -ADD -MSTORE -PUSH([101, 115, 115]) -PUSH([232]) -SHL -PUSH([100]) -DUP(3) -ADD -MSTORE -PUSH([132]) -ADD -PUSH([3, 117]) -JUMP -"] - 138 [ color=black shape=rectangle label="Bytecode range: (0x59b, 0x5b5] ---- -JUMPDEST -PUSH([1]) -PUSH([1]) -PUSH([160]) -SHL -SUB -DUP(4) -AND -PUSH([0]) -SWAP(1) -DUP(2) -MSTORE -PUSH([32]) -DUP(2) -SWAP(1) -MSTORE -PUSH([64]) -SWAP(1) -KECCAK256 -SLOAD -DUP(2) -DUP(2) -LT -ISZERO -PUSH([6, 141]) -JUMPI -"] - 139 [ color=black shape=rectangle label="Bytecode range: (0x5b5, 0x5d6] ---- -PUSH([64]) -MLOAD -PUSH([70, 27, 205]) -PUSH([229]) -SHL -DUP(2) -MSTORE -PUSH([32]) -PUSH([4]) -DUP(3) -ADD -MSTORE -PUSH([38]) -PUSH([36]) -DUP(3) -ADD -MSTORE -PUSH([69, 82, 67, 50, 48, 58, 32, 116, 114, 97, 110, 115, 102, 101, 114, 32, 97, 109, 111, 117, 110, 116, 32, 101, 120, 99, 101, 101, 100, 115, 32, 98]) -PUSH([68]) -DUP(3) -ADD -MSTORE -PUSH([97, 108, 97, 110, 99, 101]) -PUSH([208]) -SHL -PUSH([100]) -DUP(3) -ADD -MSTORE -PUSH([132]) -ADD -PUSH([3, 117]) -JUMP -"] - 140 [ color=black shape=rectangle label="Bytecode range: (0x5d6, 0x613] ---- -JUMPDEST -PUSH([1]) -PUSH([1]) -PUSH([160]) -SHL -SUB -DUP(5) -DUP(2) -AND -PUSH([0]) -DUP(2) -DUP(2) -MSTORE -PUSH([32]) -DUP(2) -DUP(2) -MSTORE -PUSH([64]) -DUP(1) -DUP(4) -KECCAK256 -DUP(8) -DUP(8) -SUB -SWAP(1) -SSTORE -SWAP(4) -DUP(8) -AND -DUP(1) -DUP(4) -MSTORE -SWAP(2) -DUP(5) -SWAP(1) -KECCAK256 -DUP(1) -SLOAD -DUP(8) -ADD -SWAP(1) -SSTORE -SWAP(3) -MLOAD -DUP(6) -DUP(2) -MSTORE -SWAP(1) -SWAP(3) -PUSH([221, 242, 82, 173, 27, 226, 200, 155, 105, 194, 176, 104, 252, 55, 141, 170, 149, 43, 167, 241, 99, 196, 161, 22, 40, 245, 90, 77, 245, 35, 179, 239]) -SWAP(2) -ADD -PUSH([64]) -MLOAD -DUP(1) -SWAP(2) -SUB -SWAP(1) -LOG(3) -PUSH([5, 73]) -JUMP -"] - 141 [ color=black shape=rectangle label="Bytecode range: (0x613, 0x621] ---- -JUMPDEST -PUSH([0]) -PUSH([32]) -DUP(1) -DUP(4) -MSTORE -DUP(4) -MLOAD -DUP(1) -PUSH([32]) -DUP(6) -ADD -MSTORE -PUSH([0]) -"] - 142 [ color=black shape=rectangle label="Bytecode range: (0x621, 0x628] ---- -JUMPDEST -DUP(2) -DUP(2) -LT -ISZERO -PUSH([7, 33]) -JUMPI -"] - 143 [ color=black shape=rectangle label="Bytecode range: (0x628, 0x638] ---- -DUP(6) -DUP(2) -ADD -DUP(4) -ADD -MLOAD -DUP(6) -DUP(3) -ADD -PUSH([64]) -ADD -MSTORE -DUP(3) -ADD -PUSH([7, 5]) -JUMP -"] - 144 [ color=black shape=rectangle label="Bytecode range: (0x638, 0x654] ---- -JUMPDEST -POP -PUSH([0]) -PUSH([64]) -DUP(3) -DUP(7) -ADD -ADD -MSTORE -PUSH([64]) -PUSH([31]) -NOT -PUSH([31]) -DUP(4) -ADD -AND -DUP(6) -ADD -ADD -SWAP(3) -POP -POP -POP -SWAP(3) -SWAP(2) -POP -POP -JUMP -"] - 145 [ color=black shape=rectangle label="Bytecode range: (0x654, 0x662] ---- -JUMPDEST -DUP(1) -CALLDATALOAD -PUSH([1]) -PUSH([1]) -PUSH([160]) -SHL -SUB -DUP(2) -AND -DUP(2) -EQ -PUSH([7, 89]) -JUMPI -"] - 146 [ color=black shape=rectangle label="Bytecode range: (0x662, 0x665] ---- -PUSH([0]) -DUP(1) -REVERT -"] - 147 [ color=black shape=rectangle label="Bytecode range: (0x665, 0x66a] ---- -JUMPDEST -SWAP(2) -SWAP(1) -POP -JUMP -"] - 148 [ color=black shape=rectangle label="Bytecode range: (0x66a, 0x675] ---- -JUMPDEST -PUSH([0]) -DUP(1) -PUSH([64]) -DUP(4) -DUP(6) -SUB -SLT -ISZERO -PUSH([7, 113]) -JUMPI -"] - 149 [ color=black shape=rectangle label="Bytecode range: (0x675, 0x678] ---- -PUSH([0]) -DUP(1) -REVERT -"] - 150 [ color=black shape=rectangle label="Bytecode range: (0x678, 0x67d] ---- -JUMPDEST -PUSH([7, 122]) -DUP(4) -PUSH([7, 66]) -JUMP -"] - 151 [ color=black shape=rectangle label="Bytecode range: (0x67d, 0x68a] ---- -JUMPDEST -SWAP(5) -PUSH([32]) -SWAP(4) -SWAP(1) -SWAP(4) -ADD -CALLDATALOAD -SWAP(4) -POP -POP -POP -JUMP -"] - 152 [ color=black shape=rectangle label="Bytecode range: (0x68a, 0x696] ---- -JUMPDEST -PUSH([0]) -DUP(1) -PUSH([0]) -PUSH([96]) -DUP(5) -DUP(7) -SUB -SLT -ISZERO -PUSH([7, 157]) -JUMPI -"] - 153 [ color=black shape=rectangle label="Bytecode range: (0x696, 0x699] ---- -PUSH([0]) -DUP(1) -REVERT -"] - 154 [ color=black shape=rectangle label="Bytecode range: (0x699, 0x69e] ---- -JUMPDEST -PUSH([7, 166]) -DUP(5) -PUSH([7, 66]) -JUMP -"] - 155 [ color=black shape=rectangle label="Bytecode range: (0x69e, 0x6a7] ---- -JUMPDEST -SWAP(3) -POP -PUSH([7, 180]) -PUSH([32]) -DUP(6) -ADD -PUSH([7, 66]) -JUMP -"] - 156 [ color=black shape=rectangle label="Bytecode range: (0x6a7, 0x6b6] ---- -JUMPDEST -SWAP(2) -POP -PUSH([64]) -DUP(5) -ADD -CALLDATALOAD -SWAP(1) -POP -SWAP(3) -POP -SWAP(3) -POP -SWAP(3) -JUMP -"] - 157 [ color=black shape=rectangle label="Bytecode range: (0x6b6, 0x6c0] ---- -JUMPDEST -PUSH([0]) -PUSH([32]) -DUP(3) -DUP(5) -SUB -SLT -ISZERO -PUSH([7, 214]) -JUMPI -"] - 158 [ color=black shape=rectangle label="Bytecode range: (0x6c0, 0x6c3] ---- -PUSH([0]) -DUP(1) -REVERT -"] - 159 [ color=black shape=rectangle label="Bytecode range: (0x6c3, 0x6c8] ---- -JUMPDEST -PUSH([7, 223]) -DUP(3) -PUSH([7, 66]) -JUMP -"] - 160 [ color=black shape=rectangle label="Bytecode range: (0x6c8, 0x6cf] ---- -JUMPDEST -SWAP(4) -SWAP(3) -POP -POP -POP -JUMP -"] - 161 [ color=black shape=rectangle label="Bytecode range: (0x6cf, 0x6da] ---- -JUMPDEST -PUSH([0]) -DUP(1) -PUSH([64]) -DUP(4) -DUP(6) -SUB -SLT -ISZERO -PUSH([7, 249]) -JUMPI -"] - 162 [ color=black shape=rectangle label="Bytecode range: (0x6da, 0x6dd] ---- -PUSH([0]) -DUP(1) -REVERT -"] - 163 [ color=black shape=rectangle label="Bytecode range: (0x6dd, 0x6e2] ---- -JUMPDEST -PUSH([8, 2]) -DUP(4) -PUSH([7, 66]) -JUMP -"] - 164 [ color=black shape=rectangle label="Bytecode range: (0x6e2, 0x6eb] ---- -JUMPDEST -SWAP(2) -POP -PUSH([8, 16]) -PUSH([32]) -DUP(5) -ADD -PUSH([7, 66]) -JUMP -"] - 165 [ color=black shape=rectangle label="Bytecode range: (0x6eb, 0x6f4] ---- -JUMPDEST -SWAP(1) -POP -SWAP(3) -POP -SWAP(3) -SWAP(1) -POP -JUMP -"] - 166 [ color=black shape=rectangle label="Bytecode range: (0x6f4, 0x6ff] ---- -JUMPDEST -PUSH([1]) -DUP(2) -DUP(2) -SHR -SWAP(1) -DUP(3) -AND -DUP(1) -PUSH([8, 45]) -JUMPI -"] - 167 [ color=black shape=rectangle label="Bytecode range: (0x6ff, 0x704] ---- -PUSH([127]) -DUP(3) -AND -SWAP(2) -POP -"] - 168 [ color=black shape=rectangle label="Bytecode range: (0x704, 0x70c] ---- -JUMPDEST -PUSH([32]) -DUP(3) -LT -DUP(2) -SUB -PUSH([8, 77]) -JUMPI -"] - 169 [ color=black shape=rectangle label="Bytecode range: (0x70c, 0x717] ---- -PUSH([78, 72, 123, 113]) -PUSH([224]) -SHL -PUSH([0]) -MSTORE -PUSH([34]) -PUSH([4]) -MSTORE -PUSH([36]) -PUSH([0]) -REVERT -"] - 170 [ color=black shape=rectangle label="Bytecode range: (0x717, 0x71d] ---- -JUMPDEST -POP -SWAP(2) -SWAP(1) -POP -JUMP -"] - 171 [ color=black shape=rectangle label="Bytecode range: (0x71d, 0x727] ---- -JUMPDEST -DUP(1) -DUP(3) -ADD -DUP(1) -DUP(3) -GT -ISZERO -PUSH([2, 124]) -JUMPI -"] - 172 [ color=black shape=rectangle label="Bytecode range: (0x727, 0x732] ---- -PUSH([78, 72, 123, 113]) -PUSH([224]) -SHL -PUSH([0]) -MSTORE -PUSH([17]) -PUSH([4]) -MSTORE -PUSH([36]) -PUSH([0]) -REVERT -"] - 0 -> 3 [ style=solid] - 55 -> 57 [ style=solid] - 3 -> 4 [ style=solid] - 4 -> 2 [ style=solid] - 1 -> 5 [ style=dashed] - 54 -> 53 [ style=solid] - 1 -> 6 [ style=dashed] - 53 -> 55 [ style=solid] - 1 -> 7 [ style=dashed] - 51 -> 42 [ style=solid] - 1 -> 8 [ style=dashed] - 49 -> 51 [ style=solid] - 1 -> 9 [ style=dashed] - 9 -> 2 [ style=solid] - 1 -> 10 [ style=dashed] - 48 -> 52 [ style=solid] - 10 -> 11 [ style=solid] - 11 -> 2 [ style=solid] - 1 -> 12 [ style=dashed] - 47 -> 36 [ style=solid] - 12 -> 13 [ style=solid] - 46 -> 31 [ style=solid] - 1 -> 14 [ style=dashed] - 45 -> 9 [ style=solid] - 14 -> 15 [ style=solid] - 44 -> 46 [ style=solid] - 1 -> 16 [ style=dashed] - 41 -> 40 [ style=solid] - 16 -> 17 [ style=solid] - 17 -> 2 [ style=solid] - 1 -> 18 [ style=dashed] - 1 -> 19 [ style=dashed] - 18 -> 19 [ style=solid] - 40 -> 42 [ style=solid] - 19 -> 20 [ style=solid] - 37 -> 39 [ style=solid] - 1 -> 21 [ style=dashed] - 21 -> 1 [ style=dashed] - 1 -> 22 [ style=dashed] - 36 -> 43 [ style=solid] - 22 -> 23 [ style=solid] - 23 -> 2 [ style=solid] - 1 -> 24 [ style=dashed] - 33 -> 35 [ style=solid] - 24 -> 25 [ style=solid] - 25 -> 2 [ style=solid] - 1 -> 26 [ style=dashed] - 31 -> 33 [ style=solid] - 1 -> 27 [ style=dashed] - 29 -> 10 [ style=solid] - 27 -> 28 [ style=solid] - 28 -> 2 [ style=solid] - 1 -> 29 [ style=dashed] - 27 -> 29 [ style=solid] - 1 -> 30 [ style=dashed] - 30 -> 1 [ style=dashed] - 1 -> 31 [ style=dashed] - 26 -> 10 [ style=solid] - 31 -> 32 [ style=solid] - 1 -> 33 [ style=dashed] - 32 -> 33 [ style=solid] - 24 -> 26 [ style=solid] - 33 -> 34 [ style=solid] - 34 -> 2 [ style=solid] - 1 -> 35 [ style=dashed] - 35 -> 1 [ style=dashed] - 1 -> 36 [ style=dashed] - 22 -> 24 [ style=solid] - 36 -> 37 [ style=solid] - 20 -> 19 [ style=solid] - 37 -> 38 [ style=solid] - 1 -> 39 [ style=dashed] - 38 -> 39 [ style=solid] - 1 -> 40 [ style=dashed] - 39 -> 40 [ style=solid] - 19 -> 21 [ style=solid] - 40 -> 41 [ style=solid] - 16 -> 18 [ style=solid] - 1 -> 42 [ style=dashed] - 1 -> 43 [ style=dashed] - 42 -> 43 [ style=solid] - 43 -> 1 [ style=dashed] - 1 -> 44 [ style=dashed] - 15 -> 9 [ style=solid] - 44 -> 45 [ style=solid] - 14 -> 16 [ style=solid] - 1 -> 46 [ style=dashed] - 13 -> 9 [ style=solid] - 1 -> 47 [ style=dashed] - 12 -> 14 [ style=solid] - 1 -> 48 [ style=dashed] - 10 -> 12 [ style=solid] - 48 -> 49 [ style=solid] - 8 -> 58 [ style=solid] - 49 -> 50 [ style=solid] - 1 -> 51 [ style=dashed] - 50 -> 51 [ style=solid] - 7 -> 44 [ style=solid] - 1 -> 52 [ style=dashed] - 1 -> 53 [ style=dashed] - 52 -> 53 [ style=solid] - 6 -> 44 [ style=solid] - 53 -> 54 [ style=solid] - 5 -> 22 [ style=solid] - 1 -> 55 [ style=dashed] - 3 -> 5 [ style=solid] - 55 -> 56 [ style=solid] - 1 -> 57 [ style=dashed] - 56 -> 57 [ style=solid] - 57 -> 1 [ style=dashed] - 1 -> 58 [ style=dashed] - 58 -> 2 [ style=solid] - 1 -> 62 [ style=dashed] - 62 -> 1 [ style=dashed] - 62 -> 63 [ style=solid] - 63 -> 1 [ style=dashed] - 63 -> 64 [ style=solid] - 64 -> 1 [ style=dashed] - 64 -> 65 [ style=solid] - 65 -> 1 [ style=dashed] - 65 -> 66 [ style=solid] - 66 -> 1 [ style=dashed] - 66 -> 67 [ style=solid] - 67 -> 1 [ style=dashed] - 67 -> 68 [ style=solid] - 68 -> 2 [ style=solid] - 1 -> 69 [ style=dashed] - 69 -> 1 [ style=dashed] - 69 -> 70 [ style=solid] - 70 -> 1 [ style=dashed] - 70 -> 71 [ style=solid] - 71 -> 1 [ style=dashed] - 71 -> 72 [ style=solid] - 72 -> 2 [ style=solid] - 1 -> 73 [ style=dashed] - 73 -> 1 [ style=dashed] - 73 -> 74 [ style=solid] - 74 -> 1 [ style=dashed] - 74 -> 75 [ style=solid] - 75 -> 1 [ style=dashed] - 75 -> 76 [ style=solid] - 76 -> 1 [ style=dashed] - 76 -> 77 [ style=solid] - 77 -> 1 [ style=dashed] - 77 -> 78 [ style=solid] - 1 -> 78 [ style=dashed] - 78 -> 2 [ style=solid] - 1 -> 79 [ style=dashed] - 79 -> 1 [ style=dashed] - 1 -> 80 [ style=dashed] - 80 -> 1 [ style=dashed] - 1 -> 81 [ style=dashed] - 81 -> 2 [ style=solid] - 1 -> 82 [ style=dashed] - 82 -> 1 [ style=dashed] - 1 -> 83 [ style=dashed] - 83 -> 1 [ style=dashed] - 1 -> 84 [ style=dashed] - 84 -> 1 [ style=dashed] - 1 -> 85 [ style=dashed] - 1 -> 86 [ style=dashed] - 85 -> 86 [ style=solid] - 86 -> 1 [ style=dashed] - 1 -> 87 [ style=dashed] - 87 -> 1 [ style=dashed] - 1 -> 88 [ style=dashed] - 88 -> 1 [ style=dashed] - 1 -> 89 [ style=dashed] - 89 -> 1 [ style=dashed] - 1 -> 90 [ style=dashed] - 90 -> 1 [ style=dashed] - 1 -> 91 [ style=dashed] - 91 -> 1 [ style=dashed] - 1 -> 92 [ style=dashed] - 92 -> 1 [ style=dashed] - 1 -> 93 [ style=dashed] - 93 -> 1 [ style=dashed] - 1 -> 94 [ style=dashed] - 94 -> 1 [ style=dashed] - 1 -> 95 [ style=dashed] - 95 -> 1 [ style=dashed] - 1 -> 96 [ style=dashed] - 96 -> 1 [ style=dashed] - 1 -> 97 [ style=dashed] - 97 -> 1 [ style=dashed] - 1 -> 98 [ style=dashed] - 98 -> 1 [ style=dashed] - 1 -> 99 [ style=dashed] - 99 -> 1 [ style=dashed] - 1 -> 100 [ style=dashed] - 100 -> 1 [ style=dashed] - 1 -> 101 [ style=dashed] - 101 -> 1 [ style=dashed] - 1 -> 102 [ style=dashed] - 102 -> 1 [ style=dashed] - 1 -> 103 [ style=dashed] - 103 -> 1 [ style=dashed] - 103 -> 104 [ style=solid] - 104 -> 1 [ style=dashed] - 104 -> 105 [ style=solid] - 105 -> 1 [ style=dashed] - 1 -> 106 [ style=dashed] - 1 -> 107 [ style=dashed] - 106 -> 107 [ style=solid] - 107 -> 1 [ style=dashed] - 107 -> 108 [ style=solid] - 1 -> 109 [ style=dashed] - 108 -> 109 [ style=solid] - 109 -> 1 [ style=dashed] - 1 -> 110 [ style=dashed] - 110 -> 1 [ style=dashed] - 1 -> 111 [ style=dashed] - 1 -> 112 [ style=dashed] - 111 -> 112 [ style=solid] - 112 -> 1 [ style=dashed] - 1 -> 113 [ style=dashed] - 113 -> 1 [ style=dashed] - 1 -> 114 [ style=dashed] - 114 -> 1 [ style=dashed] - 1 -> 115 [ style=dashed] - 115 -> 1 [ style=dashed] - 1 -> 116 [ style=dashed] - 116 -> 1 [ style=dashed] - 1 -> 117 [ style=dashed] - 117 -> 1 [ style=dashed] - 1 -> 118 [ style=dashed] - 118 -> 1 [ style=dashed] - 1 -> 119 [ style=dashed] - 119 -> 1 [ style=dashed] - 119 -> 120 [ style=solid] - 1 -> 121 [ style=dashed] - 120 -> 121 [ style=solid] - 121 -> 2 [ style=solid] - 1 -> 122 [ style=dashed] - 122 -> 1 [ style=dashed] - 1 -> 123 [ style=dashed] - 123 -> 1 [ style=dashed] - 1 -> 124 [ style=dashed] - 124 -> 1 [ style=dashed] - 124 -> 125 [ style=solid] - 125 -> 1 [ style=dashed] - 1 -> 126 [ style=dashed] - 126 -> 1 [ style=dashed] - 126 -> 127 [ style=solid] - 127 -> 1 [ style=dashed] - 1 -> 128 [ style=dashed] - 128 -> 1 [ style=dashed] - 1 -> 129 [ style=dashed] - 129 -> 1 [ style=dashed] - 129 -> 130 [ style=solid] - 130 -> 1 [ style=dashed] - 130 -> 131 [ style=solid] - 131 -> 1 [ style=dashed] - 1 -> 132 [ style=dashed] - 132 -> 1 [ style=dashed] - 1 -> 133 [ style=dashed] - 133 -> 1 [ style=dashed] - 1 -> 134 [ style=dashed] - 134 -> 1 [ style=dashed] - 134 -> 135 [ style=solid] - 135 -> 1 [ style=dashed] - 1 -> 136 [ style=dashed] - 136 -> 1 [ style=dashed] - 136 -> 137 [ style=solid] - 137 -> 1 [ style=dashed] - 1 -> 138 [ style=dashed] - 138 -> 1 [ style=dashed] - 138 -> 139 [ style=solid] - 139 -> 1 [ style=dashed] - 1 -> 140 [ style=dashed] - 140 -> 1 [ style=dashed] - 1 -> 141 [ style=dashed] - 1 -> 142 [ style=dashed] - 141 -> 142 [ style=solid] - 142 -> 1 [ style=dashed] - 142 -> 143 [ style=solid] - 143 -> 1 [ style=dashed] - 1 -> 144 [ style=dashed] - 144 -> 1 [ style=dashed] - 1 -> 145 [ style=dashed] - 145 -> 1 [ style=dashed] - 145 -> 146 [ style=solid] - 146 -> 2 [ style=solid] - 1 -> 147 [ style=dashed] - 147 -> 1 [ style=dashed] - 1 -> 148 [ style=dashed] - 148 -> 1 [ style=dashed] - 148 -> 149 [ style=solid] - 149 -> 2 [ style=solid] - 1 -> 150 [ style=dashed] - 150 -> 1 [ style=dashed] - 1 -> 151 [ style=dashed] - 151 -> 1 [ style=dashed] - 1 -> 152 [ style=dashed] - 152 -> 1 [ style=dashed] - 152 -> 153 [ style=solid] - 153 -> 2 [ style=solid] - 1 -> 154 [ style=dashed] - 154 -> 1 [ style=dashed] - 1 -> 155 [ style=dashed] - 155 -> 1 [ style=dashed] - 1 -> 156 [ style=dashed] - 156 -> 1 [ style=dashed] - 1 -> 157 [ style=dashed] - 157 -> 1 [ style=dashed] - 157 -> 158 [ style=solid] - 158 -> 2 [ style=solid] - 1 -> 159 [ style=dashed] - 159 -> 1 [ style=dashed] - 1 -> 160 [ style=dashed] - 160 -> 1 [ style=dashed] - 1 -> 161 [ style=dashed] - 161 -> 1 [ style=dashed] - 161 -> 162 [ style=solid] - 162 -> 2 [ style=solid] - 1 -> 163 [ style=dashed] - 163 -> 1 [ style=dashed] - 1 -> 164 [ style=dashed] - 164 -> 1 [ style=dashed] - 1 -> 165 [ style=dashed] - 165 -> 1 [ style=dashed] - 1 -> 166 [ style=dashed] - 166 -> 1 [ style=dashed] - 166 -> 167 [ style=solid] - 1 -> 168 [ style=dashed] - 167 -> 168 [ style=solid] - 168 -> 1 [ style=dashed] - 168 -> 169 [ style=solid] - 169 -> 2 [ style=solid] - 1 -> 170 [ style=dashed] - 170 -> 1 [ style=dashed] - 1 -> 171 [ style=dashed] - 171 -> 1 [ style=dashed] - 171 -> 172 [ style=solid] - 172 -> 2 [ style=solid] -} - diff --git a/tmp b/tmp deleted file mode 100644 index 4dc66d5..0000000 --- a/tmp +++ /dev/null @@ -1,3 +0,0 @@ -ADD -1 [stack(-1)] [stack(-1) = stack(-1) + (stack(-2)] -ADD -2 [stack(-2)] [stack(-2) = stack(-1) + (stack(-1)] -