From 06aa289d9ba8a6a07a648bcbc8f8a6741e4784cb Mon Sep 17 00:00:00 2001 From: Cyrill Leutwiler Date: Wed, 22 May 2024 21:35:32 +0200 Subject: [PATCH] Constructors and contract creation (#11) Implement constructor logic and support create/create2 in the mock runtime Signed-off-by: xermicus --- Cargo.lock | 96 +++--- Cargo.toml | 3 +- README.md | 3 +- crates/integration/Cargo.toml | 1 + crates/integration/codesize.json | 16 +- crates/integration/contracts/Create.sol | 21 ++ crates/integration/contracts/flipper.sol | 4 + crates/integration/src/cases.rs | 50 ++- crates/integration/src/mock_runtime.rs | 187 ++++++++-- crates/integration/src/tests.rs | 102 +++++- crates/llvm-context/src/polkavm/const/mod.rs | 10 +- .../src/polkavm/const/runtime_api.rs | 2 + .../src/polkavm/context/function/mod.rs | 2 +- .../context/function/runtime/deployer_call.rs | 324 ------------------ .../polkavm/context/function/runtime/entry.rs | 42 +-- .../polkavm/context/function/runtime/mod.rs | 22 +- .../llvm-context/src/polkavm/context/mod.rs | 21 +- crates/llvm-context/src/polkavm/evm/create.rs | 119 ++++--- crates/llvm-context/src/polkavm/evm/return.rs | 69 +--- .../src/polkavm/evm/return_data.rs | 107 +++--- crates/llvm-context/src/polkavm/mod.rs | 3 +- crates/pallet-contracts-pvm-llapi/Cargo.toml | 1 + .../src/calling_convention.rs | 90 +++++ crates/pallet-contracts-pvm-llapi/src/lib.rs | 54 +-- .../src/polkavm_guest.rs | 54 +++ .../statement/expression/function_call/mod.rs | 9 +- 26 files changed, 692 insertions(+), 720 deletions(-) create mode 100644 crates/integration/contracts/Create.sol delete mode 100644 crates/llvm-context/src/polkavm/context/function/runtime/deployer_call.rs create mode 100644 crates/pallet-contracts-pvm-llapi/src/calling_convention.rs create mode 100644 crates/pallet-contracts-pvm-llapi/src/polkavm_guest.rs diff --git a/Cargo.lock b/Cargo.lock index aa4caf1..55b2592 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -68,7 +68,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.65", "syn-solidity", "tiny-keccak", ] @@ -142,9 +142,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.83" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25bdb32cbbdce2b519a9cd7df3a678443100e265d5e25ca763b7572a5104f5f3" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" [[package]] name = "ark-ff" @@ -284,7 +284,7 @@ checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42" dependencies = [ "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.65", ] [[package]] @@ -394,9 +394,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.0.97" +version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "099a5357d84c4c61eb35fc8eafa9a79a902c2f76911e5747ced4e032edd8d9b4" +checksum = "41c270e7540d725e65ac7f1b212ac8ce349719624d7bcff99f8e2e488e8cf03f" [[package]] name = "cfg-if" @@ -473,7 +473,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.65", ] [[package]] @@ -500,9 +500,9 @@ dependencies = [ [[package]] name = "const-hex" -version = "1.11.3" +version = "1.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ba00838774b4ab0233e355d26710fbfc8327a05c017f6dc4873f876d1f79f78" +checksum = "70ff96486ccc291d36a958107caf2c0af8c78c0af7d31ae2f35ce055130de1a6" dependencies = [ "cfg-if", "cpufeatures", @@ -589,9 +589,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.19" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" [[package]] name = "crunchy" @@ -698,9 +698,9 @@ dependencies = [ [[package]] name = "either" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" +checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" [[package]] name = "elliptic-curve" @@ -1029,7 +1029,7 @@ dependencies = [ [[package]] name = "inkwell" version = "0.4.0" -source = "git+https://github.com/TheDan64/inkwell.git#6c0fb56b3554e939f9ca61b465043d6a84fb7b95" +source = "git+https://github.com/TheDan64/inkwell.git?rev=6c0fb56b3554e939f9ca61b465043d6a84fb7b95#6c0fb56b3554e939f9ca61b465043d6a84fb7b95" dependencies = [ "either", "inkwell_internals", @@ -1043,11 +1043,11 @@ dependencies = [ [[package]] name = "inkwell_internals" version = "0.9.0" -source = "git+https://github.com/TheDan64/inkwell.git#6c0fb56b3554e939f9ca61b465043d6a84fb7b95" +source = "git+https://github.com/TheDan64/inkwell.git?rev=6c0fb56b3554e939f9ca61b465043d6a84fb7b95#6c0fb56b3554e939f9ca61b465043d6a84fb7b95" dependencies = [ "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.65", ] [[package]] @@ -1141,9 +1141,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.154" +version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "libm" @@ -1153,9 +1153,9 @@ checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" [[package]] name = "libmimalloc-sys" -version = "0.1.37" +version = "0.1.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81eb4061c0582dedea1cbc7aff2240300dd6982e0239d1c99e65c1dbf4a30ba7" +checksum = "0e7bb23d733dfcc8af652a78b7bf232f0e967710d044732185e561e47c0336b6" dependencies = [ "cc", "libc", @@ -1163,9 +1163,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "lld-sys" @@ -1209,9 +1209,9 @@ checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" [[package]] name = "mimalloc" -version = "0.1.41" +version = "0.1.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f41a2280ded0da56c8cf898babb86e8f10651a34adcfff190ae9a1159c6908d" +checksum = "e9186d86b79b52f4a77af65604b51225e8db1d6ee7e3f41aec1e40829c71a176" dependencies = [ "libmimalloc-sys", ] @@ -1315,6 +1315,7 @@ checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" name = "pallet-contracts-pvm-llapi" version = "0.1.0" dependencies = [ + "anyhow", "inkwell", ] @@ -1379,9 +1380,9 @@ dependencies = [ [[package]] name = "plotters" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45" +checksum = "a15b6eccb8484002195a3e44fe65a4ce8e93a625797a063735536fd59cb01cf3" dependencies = [ "num-traits", "plotters-backend", @@ -1392,15 +1393,15 @@ dependencies = [ [[package]] name = "plotters-backend" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609" +checksum = "414cec62c6634ae900ea1c56128dfe87cf63e7caece0852ec76aba307cebadb7" [[package]] name = "plotters-svg" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab" +checksum = "81b30686a7d9c3e010b84284bdd26a29f2138574f52f5eb6f794fc0ad924e705" dependencies = [ "plotters-backend", ] @@ -1518,9 +1519,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.82" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b" +checksum = "0b33eb56c327dec362a9e55b3ad14f9d2f0904fb5a5b03b513ab5465399e9f43" dependencies = [ "unicode-ident", ] @@ -1732,6 +1733,7 @@ dependencies = [ "alloy-sol-types", "env_logger", "hex", + "log", "polkavm", "rayon", "revive-common", @@ -2026,7 +2028,7 @@ checksum = "6048858004bcff69094cd972ed40a32500f153bd3be9f716b2eed2e8217c4838" dependencies = [ "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.65", ] [[package]] @@ -2198,9 +2200,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.63" +version = "2.0.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf5be731623ca1a1fb7d8be6f261a3be6d3e2337b8a1f97be944d020c8fcb704" +checksum = "d2863d96a84c6439701d7a38f9de935ec562c8832cc55d1dde0f513b52fad106" dependencies = [ "proc-macro2", "quote", @@ -2216,7 +2218,7 @@ dependencies = [ "paste", "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.65", ] [[package]] @@ -2248,22 +2250,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.60" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "579e9083ca58dd9dcf91a9923bb9054071b9ebbd800b342194c9feb0ee89fc18" +checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.60" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2470041c06ec3ac1ab38d0356a6119054dedaea53e12fbefc0de730a1c08524" +checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.65", ] [[package]] @@ -2287,9 +2289,9 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.5" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" +checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" [[package]] name = "toml_edit" @@ -2414,7 +2416,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.65", "wasm-bindgen-shared", ] @@ -2436,7 +2438,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.65", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -2675,7 +2677,7 @@ checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.65", ] [[package]] @@ -2695,7 +2697,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.65", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 6ffd300..6d1b859 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,6 +36,7 @@ alloy-sol-types = "0.6" env_logger = { version = "0.10.0", default-features = false } serde_stacker = "0.1" criterion = { version = "0.5", features = ["html_reports"] } +log = { version = "0.4" } # Benchmarking against EVM primitive-types = { version = "0.12", features = ["codec"] } @@ -43,7 +44,7 @@ evm-interpreter = { git = "https://github.com/xermicus/evm.git", branch = "separ [workspace.dependencies.inkwell] git = "https://github.com/TheDan64/inkwell.git" -commit = "d916c66" +rev = "6c0fb56b3554e939f9ca61b465043d6a84fb7b95" default-features = false features = ["serde", "llvm18-0", "no-libffi-linking", "target-riscv"] diff --git a/README.md b/README.md index 8bc2fb1..d287ed3 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ The project is in a very early PoC phase. Don't yet expect the produced code to - [x] Use PolkaVM allocator for heap space - [ ] Exercice `schlau` and possibly `smart-bench` benchmark cases - [x] 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 +- [x] 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 @@ -36,3 +36,4 @@ The project is in a very early PoC phase. Don't yet expect the produced code to - [ ] Document differences from EVM - [ ] Audit for bugs and correctness - [x] Rebranding +- [ ] Remove support for vyper diff --git a/crates/integration/Cargo.toml b/crates/integration/Cargo.toml index 04bcba8..70bbf24 100644 --- a/crates/integration/Cargo.toml +++ b/crates/integration/Cargo.toml @@ -11,6 +11,7 @@ alloy-primitives = { workspace = true } alloy-sol-types = { workspace = true } hex = { workspace = true } env_logger = { workspace = true } +log = { workspace = true } revive-solidity = { path = "../solidity" } revive-differential = { path = "../differential" } diff --git a/crates/integration/codesize.json b/crates/integration/codesize.json index b8a136f..dd2771a 100644 --- a/crates/integration/codesize.json +++ b/crates/integration/codesize.json @@ -1,10 +1,10 @@ { - "Baseline": 2824, - "Computation": 6253, - "DivisionArithmetics": 41341, - "ERC20": 54329, - "Events": 3642, - "FibonacciIterative": 4866, - "Flipper": 3270, - "SHA1": 34605 + "Baseline": 934, + "Computation": 4360, + "DivisionArithmetics": 39448, + "ERC20": 52461, + "Events": 1749, + "FibonacciIterative": 2973, + "Flipper": 3368, + "SHA1": 32709 } \ No newline at end of file diff --git a/crates/integration/contracts/Create.sol b/crates/integration/contracts/Create.sol new file mode 100644 index 0000000..c7e4cef --- /dev/null +++ b/crates/integration/contracts/Create.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.24; + +contract CreateA { + address creator; + + constructor() payable { + creator = msg.sender; + } +} + +contract CreateB { + receive() external payable { + new CreateA{value: msg.value}(); + } + + fallback() external { + new CreateA{salt: hex"01"}(); + } +} diff --git a/crates/integration/contracts/flipper.sol b/crates/integration/contracts/flipper.sol index 7640099..08c48c3 100644 --- a/crates/integration/contracts/flipper.sol +++ b/crates/integration/contracts/flipper.sol @@ -4,6 +4,10 @@ pragma solidity ^0.8; contract Flipper { bool coin; + constructor(bool _coin) { + coin = _coin; + } + function flip() public { coin = !coin; } diff --git a/crates/integration/src/cases.rs b/crates/integration/src/cases.rs index 80285d0..6abd94c 100644 --- a/crates/integration/src/cases.rs +++ b/crates/integration/src/cases.rs @@ -1,5 +1,5 @@ use alloy_primitives::{I256, U256}; -use alloy_sol_types::{sol, SolCall}; +use alloy_sol_types::{sol, SolCall, SolConstructor}; use crate::mock_runtime::{CallOutput, State}; @@ -13,7 +13,11 @@ pub struct Contract { sol!(contract Baseline { function baseline() public payable; }); -sol!(contract Flipper { function flip() public; }); +sol!(contract Flipper { + constructor (bool); + + function flip() public; +}); sol!(contract Computation { function odd_product(int32 n) public pure returns (int64); @@ -113,6 +117,12 @@ sol!( } ); +sol!( + contract CreateB { + fallback() external payable; + } +); + impl Contract { /// Execute the contract. /// @@ -228,6 +238,18 @@ impl Contract { } } + pub fn flipper_constructor(coin: bool) -> Self { + let code = include_str!("../contracts/flipper.sol"); + let name = "Flipper"; + + Self { + name, + evm_runtime: crate::compile_evm_bin_runtime(name, code), + pvm_runtime: crate::compile_blob(name, code), + calldata: Flipper::constructorCall::new((coin,)).abi_encode(), + } + } + pub fn erc20() -> Self { let code = include_str!("../contracts/ERC20.sol"); let name = "ERC20"; @@ -359,6 +381,30 @@ impl Contract { calldata: Events::emitEventCall::new((topics,)).abi_encode(), } } + + pub fn create_a() -> Self { + let code = include_str!("../contracts/Create.sol"); + let name = "CreateA"; + + Self { + name, + evm_runtime: crate::compile_evm_bin_runtime(name, code), + pvm_runtime: crate::compile_blob(name, code), + calldata: vec![0; 4], + } + } + + pub fn create_b() -> Self { + let code = include_str!("../contracts/Create.sol"); + let name = "CreateB"; + + Self { + name, + evm_runtime: crate::compile_evm_bin_runtime(name, code), + pvm_runtime: crate::compile_blob(name, code), + calldata: vec![0; 4], + } + } } #[cfg(test)] diff --git a/crates/integration/src/mock_runtime.rs b/crates/integration/src/mock_runtime.rs index 817ec3d..a380983 100644 --- a/crates/integration/src/mock_runtime.rs +++ b/crates/integration/src/mock_runtime.rs @@ -10,10 +10,10 @@ use revive_llvm_context::polkavm_const::runtime_api; /// The mocked blockchain account. #[derive(Debug, Default, Clone)] -struct Account { - value: U256, - contract: Option, - storage: HashMap, +pub struct Account { + pub value: U256, + pub contract: Option, + pub storage: HashMap, } /// Emitted event data. @@ -40,7 +40,7 @@ pub struct CallOutput { enum Export { #[default] Call, - Deploy(U256), + Deploy(B256), } /// Possible contract call return flags. @@ -107,6 +107,8 @@ pub struct Transaction { } impl Transaction { + pub const CALL_STACK_SIZE: usize = 1024; + pub fn default_address() -> Address { Address::default().create2(B256::default(), keccak256([]).0) } @@ -129,10 +131,8 @@ impl Transaction { .unwrap_or_else(|| panic!("callee has no associated account: {account}")) } - fn create2(&self, salt: U256, blob_hash: U256) -> Address { - self.top_frame() - .callee - .create2(salt.to_be_bytes(), blob_hash.to_be_bytes()) + fn create2(&self, salt: B256, blob_hash: B256) -> Address { + self.top_frame().callee.create2(salt, blob_hash) } } @@ -168,10 +168,26 @@ impl TransactionBuilder { self } - /// Set the transaction to deploy code. - pub fn deploy(mut self, code: &[u8]) -> Self { + /// Helper to setup the transaction for deploy code. + /// - Simulate an upload of the `code` + /// - Set the export to `deploy` + /// - Derive address based on the caller and `salt` value + /// - Set the callee to the derived address + /// - Create a new default account at the derived address + pub fn deploy(mut self, code: &[u8], salt: Option) -> Self { let blob_hash = self.context.state.upload_code(code); + let address = self + .context + .top_frame() + .caller + .create2(salt.unwrap_or_default(), blob_hash); + self.context.top_frame_mut().export = Export::Deploy(blob_hash); + self.context.top_frame_mut().callee = address; + self.context + .state + .create_account(address, Default::default(), blob_hash); + self } @@ -183,7 +199,7 @@ impl TransactionBuilder { self.context.state.create_account( Transaction::default_address(), Default::default(), - keccak256(code).into(), + keccak256(code), ); self } @@ -193,12 +209,11 @@ impl TransactionBuilder { /// Reverts any state changes if the contract reverts or the exuection traps. pub fn call(mut self) -> (State, CallOutput) { let blob_hash = match self.context.top_frame().export { - Export::Call => self.context.top_account_mut().contract.unwrap_or_else(|| { - panic!( - "not a contract account: {}", - self.context.top_frame().callee - ) - }), + Export::Call => self + .context + .top_account_mut() + .contract + .expect("balance transfer"), Export::Deploy(blob_hash) => blob_hash, }; let code = self @@ -207,7 +222,12 @@ impl TransactionBuilder { .blobs .get(&blob_hash) .unwrap_or_else(|| panic!("contract code not found: {blob_hash}")); - let (mut instance, export) = prepare(code, None); + let (mut instance, _) = prepare(code, None); + let export = match self.context.top_frame().export { + Export::Call => runtime_api::CALL, + Export::Deploy(_) => runtime_api::DEPLOY, + }; + let export = instance.module().lookup_export(export).unwrap(); self.call_on(&mut instance, export) } @@ -223,14 +243,6 @@ impl TransactionBuilder { let mut state_args = polkavm::StateArgs::default(); state_args.set_gas(polkavm::Gas::MAX); - if let Export::Deploy(blob_hash) = self.context.top_frame().export { - self.context.state.create_account( - self.context.create2(Default::default(), blob_hash), - self.context.top_frame().callvalue, - blob_hash, - ); - } - let callvalue = self.context.top_frame().callvalue; self.context.top_account_mut().value += callvalue; @@ -271,7 +283,7 @@ impl From for TransactionBuilder { /// The mocked blockchain state. #[derive(Default, Clone, Debug)] pub struct State { - blobs: HashMap>, + blobs: HashMap>, accounts: HashMap, } @@ -279,6 +291,19 @@ impl State { pub const BLOCK_NUMBER: u64 = 123; pub const BLOCK_TIMESTAMP: u64 = 456; + pub fn new_deployed(contract: crate::Contract) -> (Self, Address) { + let (state, output) = State::default() + .transaction() + .deploy(&contract.pvm_runtime, None) + .calldata(contract.calldata) + .call(); + assert_eq!(output.flags, ReturnFlags::Success); + + let address = *state.accounts().keys().next().unwrap(); + + (state, address) + } + pub fn transaction(self) -> TransactionBuilder { TransactionBuilder { state_before: self.clone(), @@ -289,8 +314,8 @@ impl State { } } - pub fn upload_code(&mut self, code: &[u8]) -> U256 { - let blob_hash = keccak256(code).into(); + pub fn upload_code(&mut self, code: &[u8]) -> B256 { + let blob_hash = keccak256(code); self.blobs.insert(blob_hash, code.to_vec()); blob_hash } @@ -308,7 +333,7 @@ impl State { ); } - pub fn create_account(&mut self, address: Address, value: U256, blob_hash: U256) { + pub fn create_account(&mut self, address: Address, value: U256, blob_hash: B256) { self.accounts.insert( address, Account { @@ -318,6 +343,10 @@ impl State { }, ); } + + pub fn accounts(&self) -> &HashMap { + &self.accounts + } } fn link_host_functions(engine: &Engine) -> Linker { @@ -608,6 +637,99 @@ fn link_host_functions(engine: &Engine) -> Linker { ) .unwrap(); + linker + .func_wrap( + runtime_api::INSTANTIATE, + |caller: Caller, argument_ptr: u32| { + let (mut caller, transaction) = caller.split(); + + #[derive(Debug)] + #[repr(packed)] + struct Arguments { + code_hash_ptr: u32, + ref_time_limit: u64, + proof_size_limit: u64, + deposit_ptr: u32, + value_ptr: u32, + input_data_ptr: u32, + input_data_len: u32, + address_ptr: u32, + address_len_ptr: u32, + output_ptr: u32, + output_len_ptr: u32, + salt_ptr: u32, + salt_len: u32, + } + let mut buffer = [0; std::mem::size_of::()]; + caller.read_memory_into_slice(argument_ptr, &mut buffer)?; + let arguments: Arguments = unsafe { std::mem::transmute(buffer) }; + + assert_eq!({ arguments.ref_time_limit }, 0); + assert_eq!({ arguments.proof_size_limit }, 0); + assert_eq!({ arguments.deposit_ptr }, u32::MAX); + assert_eq!({ arguments.output_ptr }, u32::MAX); + assert_eq!({ arguments.output_len_ptr }, u32::MAX); + assert_eq!({ arguments.salt_len }, 32); + + if transaction.stack.len() >= Transaction::CALL_STACK_SIZE { + log::info!("deployment faild: maximum stack depth reached"); + caller.write_memory(arguments.address_ptr, &Address::ZERO.0 .0)?; + return Ok(()); + } + + let blob_hash = caller.read_memory_into_vec(arguments.code_hash_ptr, 32)?; + let blob_hash = B256::from_slice(&blob_hash); + let value = caller.read_memory_into_vec(arguments.value_ptr, 20)?; + let input_data = caller + .read_memory_into_vec(arguments.input_data_ptr, arguments.input_data_len)?; + + let address_len = caller.read_u32(arguments.address_len_ptr)?; + assert_eq!(address_len, 20); + + let salt = caller.read_memory_into_vec(arguments.salt_ptr, arguments.salt_len)?; + let salt = B256::from_slice(&salt); + let address = transaction.create2(salt, blob_hash); + if transaction.state.accounts.contains_key(&address) { + log::info!("deployment failed: address {address} already exists"); + caller.write_memory(arguments.address_ptr, &Address::ZERO.0 .0)?; + return Ok(()); + } + + let amount = U256::from_le_slice(&value); + match transaction.top_account_mut().value.checked_sub(amount) { + Some(deducted) => transaction.top_account_mut().value = deducted, + None => { + log::info!("deployment failed: insufficient balance {amount}"); + caller.write_memory(arguments.address_ptr, &Address::ZERO.0 .0)?; + return Ok(()); + } + } + + let (state, output) = transaction + .state + .clone() + .transaction() + .callee(address) + .deploy(transaction.state.blobs.get(&blob_hash).unwrap(), Some(salt)) + .callvalue(amount) + .calldata(input_data) + .call(); + + let result = if output.flags == ReturnFlags::Success { + log::info!("deployment succeeded"); + transaction.state = state; + address + } else { + log::info!("deployment failed: callee reverted {:?}", output.flags); + Address::ZERO + }; + caller.write_memory(arguments.address_ptr, &result.0 .0)?; + + Ok(()) + }, + ) + .unwrap(); + linker } @@ -634,7 +756,8 @@ pub fn instantiate_module( } pub fn prepare(code: &[u8], config: Option) -> (Instance, ExportIndex) { - let blob = ProgramBlob::parse(code.into()).unwrap(); + let blob = ProgramBlob::parse(code.into()) + .unwrap_or_else(|err| panic!("{err}\n{}", hex::encode(code))); let engine = Engine::new(&config.unwrap_or_default()).unwrap(); diff --git a/crates/integration/src/tests.rs b/crates/integration/src/tests.rs index 9c14fee..c746433 100644 --- a/crates/integration/src/tests.rs +++ b/crates/integration/src/tests.rs @@ -1,4 +1,4 @@ -use alloy_primitives::{keccak256, Address, FixedBytes, I256, U256}; +use alloy_primitives::{keccak256, Address, FixedBytes, B256, I256, U256}; use alloy_sol_types::{sol, SolCall}; use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; use sha1::Digest; @@ -27,16 +27,24 @@ fn fibonacci() { #[test] fn flipper() { + let (state, address) = State::new_deployed(Contract::flipper_constructor(true)); + let contract = Contract::flipper(); - let address = Transaction::default_address(); - - let (state, output) = contract.execute(); - assert_eq!(output.flags, ReturnFlags::Success); - state.assert_storage_key(address, U256::ZERO, U256::from(1)); - - let (state, output) = state.transaction().calldata(contract.calldata).call(); + let (state, output) = state + .transaction() + .calldata(contract.calldata.clone()) + .callee(address) + .call(); assert_eq!(output.flags, ReturnFlags::Success); state.assert_storage_key(address, U256::ZERO, U256::ZERO); + + let (state, output) = state + .transaction() + .calldata(contract.calldata) + .callee(address) + .call(); + assert_eq!(output.flags, ReturnFlags::Success); + state.assert_storage_key(address, U256::ZERO, U256::from(1)); } #[test] @@ -421,3 +429,81 @@ fn events() { assert_success(&Contract::event(U256::ZERO), true); assert_success(&Contract::event(U256::from(123)), true); } + +#[test] +fn create2() { + let mut state = State::default(); + let contract_a = Contract::create_a(); + state.upload_code(&contract_a.pvm_runtime); + + let contract = Contract::create_b(); + let (state, output) = state + .transaction() + .with_default_account(&contract.pvm_runtime) + .calldata(contract.calldata) + .call(); + + assert_eq!(output.flags, ReturnFlags::Success); + assert_eq!(state.accounts().len(), 2); + + for address in state.accounts().keys() { + if *address != Transaction::default_address() { + let derived_address = Transaction::default_address().create2( + B256::from(U256::from(1)), + keccak256(&contract_a.pvm_runtime).0, + ); + assert_eq!(*address, derived_address); + } + } +} + +#[test] +fn create2_failure() { + let mut state = State::default(); + let contract_a = Contract::create_a(); + state.upload_code(&contract_a.pvm_runtime); + + let contract = Contract::create_b(); + let (state, output) = state + .transaction() + .with_default_account(&contract.pvm_runtime) + .calldata(contract.calldata.clone()) + .call(); + + assert_eq!(output.flags, ReturnFlags::Success); + + // The address already exists, which should cause the contract to revert + + let (_, output) = state + .transaction() + .with_default_account(&contract.pvm_runtime) + .calldata(contract.calldata) + .call(); + + assert_eq!(output.flags, ReturnFlags::Revert); +} + +#[test] +fn create_with_value() { + let mut state = State::default(); + state.upload_code(&Contract::create_a().pvm_runtime); + let amount = U256::from(123); + + let contract = Contract::create_b(); + let (state, output) = state + .transaction() + .with_default_account(&contract.pvm_runtime) + .callvalue(amount) + .call(); + + assert_eq!(output.flags, ReturnFlags::Success); + assert_eq!(state.accounts().len(), 2); + + for (address, account) in state.accounts() { + if *address == Transaction::default_address() { + assert_eq!(account.value, U256::ZERO); + } else { + assert_eq!(account.value, amount); + } + } +} diff --git a/crates/llvm-context/src/polkavm/const/mod.rs b/crates/llvm-context/src/polkavm/const/mod.rs index 6f4c251..9cc454f 100644 --- a/crates/llvm-context/src/polkavm/const/mod.rs +++ b/crates/llvm-context/src/polkavm/const/mod.rs @@ -33,9 +33,6 @@ 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_"; @@ -74,10 +71,5 @@ pub const NO_SYSTEM_CALL_BIT: bool = false; 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 = - revive_common::BYTE_LENGTH_X32 + (revive_common::BYTE_LENGTH_WORD * 4); +pub const DEPLOYER_CALL_HEADER_SIZE: usize = revive_common::BYTE_LENGTH_WORD; diff --git a/crates/llvm-context/src/polkavm/const/runtime_api.rs b/crates/llvm-context/src/polkavm/const/runtime_api.rs index 9a81614..3f9891d 100644 --- a/crates/llvm-context/src/polkavm/const/runtime_api.rs +++ b/crates/llvm-context/src/polkavm/const/runtime_api.rs @@ -24,6 +24,8 @@ pub static HASH_KECCAK_256: &str = "hash_keccak_256"; pub static INPUT: &str = "input"; +pub static INSTANTIATE: &str = "instantiate"; + pub static NOW: &str = "now"; pub static RETURN: &str = "seal_return"; diff --git a/crates/llvm-context/src/polkavm/context/function/mod.rs b/crates/llvm-context/src/polkavm/context/function/mod.rs index 7425f36..135fbc7 100644 --- a/crates/llvm-context/src/polkavm/context/function/mod.rs +++ b/crates/llvm-context/src/polkavm/context/function/mod.rs @@ -128,7 +128,7 @@ impl<'ctx> Function<'ctx> { ) { for attribute_kind in attributes.into_iter() { match attribute_kind { - Attribute::Memory => todo!("`memory` attributes are not yet implemented"), + Attribute::Memory => unimplemented!("`memory` attributes are not implemented"), attribute_kind @ Attribute::AlwaysInline if force => { let is_optimize_none_set = declaration .value diff --git a/crates/llvm-context/src/polkavm/context/function/runtime/deployer_call.rs b/crates/llvm-context/src/polkavm/context/function/runtime/deployer_call.rs deleted file mode 100644 index c6383db..0000000 --- a/crates/llvm-context/src/polkavm/context/function/runtime/deployer_call.rs +++ /dev/null @@ -1,324 +0,0 @@ -//! The `deployer_call` function. - -use inkwell::types::BasicType; - -use crate::polkavm::context::address_space::AddressSpace; -use crate::polkavm::context::function::Function; -use crate::polkavm::context::pointer::Pointer; -use crate::polkavm::context::Context; -use crate::polkavm::Dependency; -use crate::polkavm::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.word_type().as_basic_type_enum(), - context.word_type().as_basic_type_enum(), - context.word_type().as_basic_type_enum(), - context.word_type().as_basic_type_enum(), - context.word_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::polkavm::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.word_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.word_const(revive_common::BYTE_LENGTH_X32 as u64), - "deployer_call_salt_offset", - )?; - let salt_pointer = Pointer::new_with_offset( - context, - self.address_space, - context.word_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.word_const((revive_common::BYTE_LENGTH_WORD * 2) as u64), - "deployer_call_arguments_offset_offset", - )?; - let arguments_offset_pointer = Pointer::new_with_offset( - context, - self.address_space, - context.word_type(), - arguments_offset_offset, - "deployer_call_arguments_offset_pointer", - ); - context.build_store( - arguments_offset_pointer, - context.word_const( - (crate::polkavm::DEPLOYER_CALL_HEADER_SIZE - - (revive_common::BYTE_LENGTH_X32 + revive_common::BYTE_LENGTH_WORD)) - as u64, - ), - )?; - - let arguments_length_offset = context.builder().build_int_add( - arguments_offset_offset, - context.word_const(revive_common::BYTE_LENGTH_WORD as u64), - "deployer_call_arguments_length_offset", - )?; - let arguments_length_pointer = Pointer::new_with_offset( - context, - self.address_space, - context.word_type(), - arguments_length_offset, - "deployer_call_arguments_length_pointer", - ); - let arguments_length_value = context.builder().build_int_sub( - input_length, - context.word_const(crate::polkavm::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.word_type(), "deployer_call_result_pointer"); - context.build_store(result_pointer, context.word_const(0))?; - let deployer_call_result_type = context.structure_type(&[ - context - .llvm() - .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.word_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::polkavm::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::polkavm::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::polkavm::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.word_const(0), - context - .integer_type(revive_common::BIT_LENGTH_X32) - .const_zero(), - ], - context - .llvm() - .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.word_const(0), - context - .integer_type(revive_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.word_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::polkavm::GLOBAL_RETURN_DATA_POINTER, - ); - context.write_abi_data_size( - result_abi_data_pointer, - crate::polkavm::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/polkavm/context/function/runtime/entry.rs b/crates/llvm-context/src/polkavm/context/function/runtime/entry.rs index f53f959..1721cbe 100644 --- a/crates/llvm-context/src/polkavm/context/function/runtime/entry.rs +++ b/crates/llvm-context/src/polkavm/context/function/runtime/entry.rs @@ -1,6 +1,7 @@ //! The entry function. use inkwell::types::BasicType; +use inkwell::values::BasicValue; use crate::polkavm::context::address_space::AddressSpace; use crate::polkavm::context::function::runtime::Runtime; @@ -8,7 +9,6 @@ use crate::polkavm::context::Context; use crate::polkavm::r#const::*; use crate::polkavm::Dependency; use crate::polkavm::WriteLLVM; -use crate::PolkaVMPointer as Pointer; /// The entry function. /// The function is a wrapper managing the runtime and deploy code calling logic. @@ -23,8 +23,11 @@ impl Entry { /// The number of mandatory arguments. pub const MANDATORY_ARGUMENTS_COUNT: usize = 2; - /// Reserve 1kb for calldata. - pub const MAX_CALLDATA_SIZE: usize = 1024; + /// Reserve 1mb for calldata. + pub const MAX_CALLDATA_SIZE: usize = 1024 * 1024; + + /// Reserve 1mb for returndata. + pub const MAX_RETURNDATA_SIZE: usize = 1024 * 1024; /// Initializes the global variables. /// The pointers are not initialized, because it's not possible to create a null pointer. @@ -40,6 +43,14 @@ impl Entry { calldata_type.get_undef(), ); + let returndata_type = context.array_type(context.byte_type(), Self::MAX_RETURNDATA_SIZE); + context.set_global( + crate::polkavm::GLOBAL_RETURN_DATA_POINTER, + returndata_type, + AddressSpace::Stack, + returndata_type.get_undef(), + ); + context.set_global( crate::polkavm::GLOBAL_HEAP_MEMORY_POINTER, context.llvm().ptr_type(AddressSpace::Generic.into()), @@ -61,9 +72,9 @@ impl Entry { ); context.set_global( crate::polkavm::GLOBAL_RETURN_DATA_SIZE, - context.word_type(), + context.xlen_type(), AddressSpace::Stack, - context.word_const(0), + context.xlen_type().const_zero().as_basic_value_enum(), ); context.set_global( @@ -162,27 +173,6 @@ impl Entry { 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 - .llvm() - .ptr_type(AddressSpace::Generic.into()) - .as_basic_type_enum(), - "return_data_abi_initializer", - ); - context.write_abi_pointer( - calldata_end_pointer, - crate::polkavm::GLOBAL_RETURN_DATA_POINTER, - ); - context.write_abi_pointer(calldata_end_pointer, crate::polkavm::GLOBAL_ACTIVE_POINTER); - Ok(()) } diff --git a/crates/llvm-context/src/polkavm/context/function/runtime/mod.rs b/crates/llvm-context/src/polkavm/context/function/runtime/mod.rs index 66e360c..fee5a23 100644 --- a/crates/llvm-context/src/polkavm/context/function/runtime/mod.rs +++ b/crates/llvm-context/src/polkavm/context/function/runtime/mod.rs @@ -2,7 +2,6 @@ pub mod default_call; pub mod deploy_code; -pub mod deployer_call; pub mod entry; pub mod runtime_code; @@ -13,14 +12,13 @@ use crate::polkavm::Dependency; use crate::polkavm::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, + _address_space: AddressSpace, } impl Runtime { @@ -34,8 +32,8 @@ impl Runtime { pub const FUNCTION_RUNTIME_CODE: &'static str = "__runtime"; /// A shortcut constructor. - pub fn new(address_space: AddressSpace) -> Self { - Self { address_space } + pub fn new(_address_space: AddressSpace) -> Self { + Self { _address_space } } /// Returns the corresponding runtime function. @@ -52,18 +50,6 @@ impl Runtime { .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 @@ -74,7 +60,6 @@ where //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(()) } @@ -83,7 +68,6 @@ where //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/polkavm/context/mod.rs b/crates/llvm-context/src/polkavm/context/mod.rs index 442ff65..0c5ab66 100644 --- a/crates/llvm-context/src/polkavm/context/mod.rs +++ b/crates/llvm-context/src/polkavm/context/mod.rs @@ -136,7 +136,9 @@ where module: &inkwell::module::Module<'ctx>, ) { module - .link_in_module(pallet_contracts_pvm_llapi::module(llvm, "polkavm_guest").unwrap()) + .link_in_module( + pallet_contracts_pvm_llapi::polkavm_guest::module(llvm, "polkavm_guest").unwrap(), + ) .expect("the PolkaVM guest API module should be linkable"); for export in runtime_api::EXPORTS { @@ -164,7 +166,7 @@ where size: u32, ) { module - .link_in_module(pallet_contracts_pvm_llapi::min_stack_size( + .link_in_module(pallet_contracts_pvm_llapi::polkavm_guest::min_stack_size( llvm, "polkavm_stack_size", size, @@ -1139,26 +1141,13 @@ where 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. + /// Builds a contract exit sequence. 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 offset_truncated = self.safe_truncate_int_to_xlen(offset)?; let length_truncated = self.safe_truncate_int_to_xlen(length)?; let offset_into_heap = self.build_heap_gep(offset_truncated, length_truncated)?; diff --git a/crates/llvm-context/src/polkavm/evm/create.rs b/crates/llvm-context/src/polkavm/evm/create.rs index 32c6d44..5a52b00 100644 --- a/crates/llvm-context/src/polkavm/evm/create.rs +++ b/crates/llvm-context/src/polkavm/evm/create.rs @@ -5,9 +5,9 @@ use num::Zero; use crate::polkavm::context::argument::Argument; use crate::polkavm::context::code_type::CodeType; -use crate::polkavm::context::function::runtime::Runtime; use crate::polkavm::context::Context; use crate::polkavm::Dependency; +use crate::polkavm_const::runtime_api; /// Translates the contract `create` instruction. /// The instruction is simulated by a call to a system contract. @@ -20,32 +20,10 @@ pub fn create<'ctx, D>( where D: Dependency + Clone, { - let signature_hash_string = - crate::polkavm::utils::keccak256(crate::polkavm::DEPLOYER_SIGNATURE_CREATE.as_bytes()); - let signature_hash = context.word_const_str_hex(signature_hash_string.as_str()); - - let salt = context.word_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) + self::create2(context, value, input_offset, input_length, None) } /// 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>, @@ -56,28 +34,72 @@ pub fn create2<'ctx, D>( where D: Dependency + Clone, { - let signature_hash_string = - crate::polkavm::utils::keccak256(crate::polkavm::DEPLOYER_SIGNATURE_CREATE2.as_bytes()); - let signature_hash = context.word_const_str_hex(signature_hash_string.as_str()); + let input_offset = context.safe_truncate_int_to_xlen(input_offset)?; + let input_length = context.safe_truncate_int_to_xlen(input_length)?; - let salt = salt.unwrap_or_else(|| context.word_const(0)); + let value_pointer = context.build_alloca(context.value_type(), "value"); + context.build_store(value_pointer, value)?; - 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"); + let code_hash_pointer = context.build_heap_gep(input_offset, input_length)?; - Ok(result) + let input_data_pointer = context.build_gep( + code_hash_pointer, + &[context + .xlen_type() + .const_int(revive_common::BYTE_LENGTH_WORD as u64, false)], + context.byte_type(), + "value_ptr_parameter_offset", + ); + + let salt_pointer = context.build_alloca(context.word_type(), "salt"); + context.build_store(salt_pointer, salt.unwrap_or_else(|| context.word_const(0)))?; + + let (address_pointer, address_length_pointer) = + context.build_stack_parameter(revive_common::BIT_LENGTH_ETH_ADDRESS, "address_pointer"); + + let sentinel = context + .xlen_type() + .const_all_ones() + .const_to_pointer(context.llvm().ptr_type(Default::default())); + + let argument_pointer = pallet_contracts_pvm_llapi::calling_convention::Spill::new( + context.builder(), + pallet_contracts_pvm_llapi::calling_convention::instantiate(context.llvm()), + "create2_arguments", + )? + .next(code_hash_pointer.value)? + .skip() + .skip() + .next(sentinel)? + .next(value_pointer.value)? + .next(input_data_pointer.value)? + .next(input_length)? + .next(address_pointer.value)? + .next(address_length_pointer.value)? + .next(sentinel)? + .next(sentinel)? + .next(salt_pointer.value)? + .next( + context + .xlen_type() + .const_int(revive_common::BYTE_LENGTH_WORD as u64, false), + )? + .done(); + + context.builder().build_direct_call( + context.runtime_api_method(runtime_api::INSTANTIATE), + &[context + .builder() + .build_ptr_to_int(argument_pointer, context.xlen_type(), "argument_pointer")? + .into()], + "create2", + )?; + + context.build_load_word( + address_pointer, + revive_common::BIT_LENGTH_ETH_ADDRESS, + "address", + ) } /// Translates the contract hash instruction, which is actually used to set the hash of the contract @@ -121,15 +143,8 @@ where 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. +/// Translates the deploy call header size instruction. the header consists of +/// the hash of the bytecode of the contract whose instance is being created. /// Represents `datasize` in Yul and `PUSH #[$]` in the EVM legacy assembly. pub fn header_size<'ctx, D>( context: &mut Context<'ctx, D>, diff --git a/crates/llvm-context/src/polkavm/evm/return.rs b/crates/llvm-context/src/polkavm/evm/return.rs index b29c2d5..8908ce1 100644 --- a/crates/llvm-context/src/polkavm/evm/return.rs +++ b/crates/llvm-context/src/polkavm/evm/return.rs @@ -1,13 +1,9 @@ //! Translates the transaction return operations. -use crate::polkavm::context::address_space::AddressSpace; -use crate::polkavm::context::code_type::CodeType; -use crate::polkavm::context::pointer::Pointer; use crate::polkavm::context::Context; use crate::polkavm::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>, @@ -16,66 +12,15 @@ pub fn r#return<'ctx, D>( 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.word_type(), - context.word_const(crate::polkavm::HEAP_AUX_OFFSET_CONSTRUCTOR_RETURN_DATA), - "immutables_offset_pointer", - ); - context.build_store( - immutables_offset_pointer, - context.word_const(revive_common::BYTE_LENGTH_WORD as u64), - )?; - - let immutables_number_pointer = Pointer::new_with_offset( - context, - AddressSpace::HeapAuxiliary, - context.word_type(), - context.word_const( - crate::polkavm::HEAP_AUX_OFFSET_CONSTRUCTOR_RETURN_DATA - + (revive_common::BYTE_LENGTH_WORD as u64), - ), - "immutables_number_pointer", - ); - let immutable_values_size = context.immutables_size()?; - context.build_store( - immutables_number_pointer, - context - .word_const((immutable_values_size / revive_common::BYTE_LENGTH_WORD) as u64), - )?; - let immutables_size = context.builder().build_int_mul( - context.word_const(immutable_values_size as u64), - context.word_const(2), - "immutables_size", - )?; - let return_data_length = context.builder().build_int_add( - immutables_size, - context.word_const((revive_common::BYTE_LENGTH_WORD * 2) as u64), - "return_data_length", - )?; - - context.build_exit( - context.integer_const(crate::polkavm::XLEN, 0), - context.word_const(crate::polkavm::HEAP_AUX_OFFSET_CONSTRUCTOR_RETURN_DATA), - return_data_length, - )?; - } - Some(CodeType::Runtime) => { - context.build_exit( - context.integer_const(crate::polkavm::XLEN, 0), - offset, - length, - )?; - } + if context.code_type().is_none() { + anyhow::bail!("Return is not available if the contract part is undefined"); } - Ok(()) + context.build_exit( + context.integer_const(crate::polkavm::XLEN, 0), + offset, + length, + ) } /// Translates the `revert` instruction. diff --git a/crates/llvm-context/src/polkavm/evm/return_data.rs b/crates/llvm-context/src/polkavm/evm/return_data.rs index 99d45a0..6d0f6b1 100644 --- a/crates/llvm-context/src/polkavm/evm/return_data.rs +++ b/crates/llvm-context/src/polkavm/evm/return_data.rs @@ -1,10 +1,7 @@ //! Translates the return data instructions. -use inkwell::types::BasicType; use inkwell::values::BasicValue; -use crate::polkavm::context::address_space::AddressSpace; -use crate::polkavm::context::pointer::Pointer; use crate::polkavm::context::Context; use crate::polkavm::Dependency; @@ -15,13 +12,19 @@ pub fn size<'ctx, D>( where D: Dependency + Clone, { - match context.get_global_value(crate::polkavm::GLOBAL_RETURN_DATA_SIZE) { - Ok(global) => Ok(global), - Err(_error) => Ok(context.word_const(0).as_basic_value_enum()), - } + let value = context + .get_global_value(crate::polkavm::GLOBAL_RETURN_DATA_SIZE)? + .into_int_value(); + Ok(context + .builder() + .build_int_z_extend(value, context.word_type(), "calldatasize_extended")? + .as_basic_value_enum()) } -/// Translates the return data copy. +/// Translates the return data copy, trapping if +/// - Destination, offset or size exceed the VM register size (XLEN) +/// - `source_offset + size` overflows (in XLEN) +/// - `source_offset + size` is beyond `RETURNDATASIZE` pub fn copy<'ctx, D>( context: &mut Context<'ctx, D>, destination_offset: inkwell::values::IntValue<'ctx>, @@ -31,57 +34,59 @@ pub fn copy<'ctx, D>( 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 source_offset = context.safe_truncate_int_to_xlen(source_offset)?; + let destination_offset = context.safe_truncate_int_to_xlen(destination_offset)?; + let size = context.safe_truncate_int_to_xlen(size)?; - 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( + let block_copy = context.append_basic_block("copy_block"); + let block_trap = context.append_basic_block("trap_block"); + let block_check_out_of_bounds = context.append_basic_block("check_out_of_bounds_block"); + let is_overflow = context.builder().build_int_compare( inkwell::IntPredicate::UGT, - copy_slice_end, - return_data_size, - "return_data_copy_is_out_of_bounds", + source_offset, + context.builder().build_int_sub( + context.xlen_type().const_all_ones(), + size, + "offset_plus_size_max_value", + )?, + "is_returndata_size_out_of_bounds", )?; - context.build_conditional_branch(is_copy_out_of_bounds, error_block, join_block)?; + context.build_conditional_branch(is_overflow, block_trap, block_check_out_of_bounds)?; - context.set_basic_block(error_block); - crate::polkavm::evm::r#return::revert(context, context.word_const(0), context.word_const(0))?; + context.set_basic_block(block_check_out_of_bounds); + let is_out_of_bounds = context.builder().build_int_compare( + inkwell::IntPredicate::UGT, + context.builder().build_int_add( + source_offset, + context + .get_global_value(crate::polkavm::GLOBAL_RETURN_DATA_SIZE)? + .into_int_value(), + "returndata_end_pointer", + )?, + context + .xlen_type() + .const_int(crate::PolkaVMEntryFunction::MAX_CALLDATA_SIZE as u64, false), + "is_return_data_copy_overflow", + )?; + context.build_conditional_branch(is_out_of_bounds, block_trap, block_copy)?; - 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::polkavm::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.set_basic_block(block_trap); + context.build_call(context.intrinsics().trap, &[], "invalid_returndata_copy"); + context.build_unreachable(); + context.set_basic_block(block_copy); context.build_memcpy( context.intrinsics().memory_copy_from_generic, - destination, - source, + context.build_heap_gep(destination_offset, size)?, + context.build_gep( + context + .get_global(crate::polkavm::GLOBAL_RETURN_DATA_POINTER)? + .into(), + &[context.xlen_type().const_zero(), source_offset], + context.byte_type(), + "source_offset_gep", + ), size, "return_data_copy_memcpy_from_return_data", - )?; - - todo!("Build heap GEP to allocate if necessary") + ) } diff --git a/crates/llvm-context/src/polkavm/mod.rs b/crates/llvm-context/src/polkavm/mod.rs index 4415762..c5549fb 100644 --- a/crates/llvm-context/src/polkavm/mod.rs +++ b/crates/llvm-context/src/polkavm/mod.rs @@ -7,6 +7,7 @@ pub mod metadata_hash; pub mod utils; pub use self::r#const::*; +use self::utils::keccak256; use crate::debug_config::DebugConfig; use crate::optimizer::settings::Settings as OptimizerSettings; @@ -56,7 +57,7 @@ pub fn build_assembly_text( assembly_text.to_owned(), metadata_hash, bytecode.to_owned(), - Default::default(), + keccak256(bytecode), )) } diff --git a/crates/pallet-contracts-pvm-llapi/Cargo.toml b/crates/pallet-contracts-pvm-llapi/Cargo.toml index c337855..d80beb7 100644 --- a/crates/pallet-contracts-pvm-llapi/Cargo.toml +++ b/crates/pallet-contracts-pvm-llapi/Cargo.toml @@ -4,4 +4,5 @@ version = "0.1.0" edition = "2021" [dependencies] +anyhow = { workspace = true } inkwell = { workspace = true, features = ["target-riscv", "no-libffi-linking", "llvm18-0"] } \ No newline at end of file diff --git a/crates/pallet-contracts-pvm-llapi/src/calling_convention.rs b/crates/pallet-contracts-pvm-llapi/src/calling_convention.rs new file mode 100644 index 0000000..b73ffc4 --- /dev/null +++ b/crates/pallet-contracts-pvm-llapi/src/calling_convention.rs @@ -0,0 +1,90 @@ +use inkwell::{ + builder::Builder, + context::Context, + types::{BasicType, StructType}, + values::{BasicValue, PointerValue}, +}; + +pub struct Spill<'ctx> { + pointer: PointerValue<'ctx>, + builder: &'ctx Builder<'ctx>, + r#type: StructType<'ctx>, + current_field: u32, +} + +impl<'ctx> Spill<'ctx> { + pub fn new( + builder: &'ctx Builder<'ctx>, + r#type: StructType<'ctx>, + name: &str, + ) -> anyhow::Result { + Ok(Self { + pointer: builder.build_alloca(r#type, name)?, + builder, + r#type, + current_field: 0, + }) + } + + pub fn next>(mut self, value: V) -> anyhow::Result { + let field_pointer = self.builder.build_struct_gep( + self.r#type, + self.pointer, + self.current_field, + &format!("spill_parameter_{}", self.current_field), + )?; + self.builder.build_store(field_pointer, value)?; + self.current_field += 1; + Ok(self) + } + + pub fn skip(mut self) -> Self { + self.current_field += 1; + self + } + + pub fn done(self) -> PointerValue<'ctx> { + assert!( + self.r#type + .get_field_type_at_index(self.current_field) + .is_none(), + "there must not be any missing parameters" + ); + + self.pointer + } +} + +pub fn instantiate(context: &Context) -> StructType { + context.struct_type( + &[ + // code_hash_ptr: u32, + context.ptr_type(Default::default()).as_basic_type_enum(), + // ref_time_limit: u64, + context.i64_type().as_basic_type_enum(), + // proof_size_limit: u64, + context.i64_type().as_basic_type_enum(), + // deposit_ptr: u32, + context.ptr_type(Default::default()).as_basic_type_enum(), + // value_ptr: u32, + context.ptr_type(Default::default()).as_basic_type_enum(), + // input_data_ptr: u32, + context.ptr_type(Default::default()).as_basic_type_enum(), + // input_data_len: u32, + context.i32_type().as_basic_type_enum(), + // address_ptr: u32, + context.ptr_type(Default::default()).as_basic_type_enum(), + // address_len_ptr: u32, + context.ptr_type(Default::default()).as_basic_type_enum(), + // output_ptr: u32, + context.ptr_type(Default::default()).as_basic_type_enum(), + // output_len_ptr: u32, + context.ptr_type(Default::default()).as_basic_type_enum(), + // salt_ptr: u32, + context.ptr_type(Default::default()).as_basic_type_enum(), + // salt_len: u32 + context.i32_type().as_basic_type_enum(), + ], + true, + ) +} diff --git a/crates/pallet-contracts-pvm-llapi/src/lib.rs b/crates/pallet-contracts-pvm-llapi/src/lib.rs index 8f16171..34648d5 100644 --- a/crates/pallet-contracts-pvm-llapi/src/lib.rs +++ b/crates/pallet-contracts-pvm-llapi/src/lib.rs @@ -1,52 +1,2 @@ -//! 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) -} - -/// Creates a module that sets the PolkaVM minimum stack size to [`size`] if linked in. -pub fn min_stack_size<'context>( - context: &'context Context, - module_name: &str, - size: u32, -) -> Module<'context> { - let module = context.create_module(module_name); - module.set_inline_assembly(&format!( - ".pushsection .polkavm_min_stack_size,\"\",@progbits - .word {size} - .popsection" - )); - module -} - -#[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()); - } -} +pub mod calling_convention; +pub mod polkavm_guest; diff --git a/crates/pallet-contracts-pvm-llapi/src/polkavm_guest.rs b/crates/pallet-contracts-pvm-llapi/src/polkavm_guest.rs new file mode 100644 index 0000000..77bbccb --- /dev/null +++ b/crates/pallet-contracts-pvm-llapi/src/polkavm_guest.rs @@ -0,0 +1,54 @@ +//! 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) +} + +/// Creates a module that sets the PolkaVM minimum stack size to [`size`] if linked in. +pub fn min_stack_size<'context>( + context: &'context Context, + module_name: &str, + size: u32, +) -> Module<'context> { + let module = context.create_module(module_name); + module.set_inline_assembly(&format!( + ".pushsection .polkavm_min_stack_size,\"\",@progbits + .word {size} + .popsection" + )); + module +} + +#[cfg(test)] +mod tests { + use crate::polkavm_guest; + + #[test] + fn it_works() { + inkwell::targets::Target::initialize_riscv(&Default::default()); + let context = inkwell::context::Context::create(); + let module = polkavm_guest::module(&context, "polkavm_guest").unwrap(); + + assert!(module.get_function("call").is_some()); + assert!(module.get_function("deploy").is_some()); + } +} 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 index 1c51775..4beefd1 100644 --- a/crates/solidity/src/yul/parser/statement/expression/function_call/mod.rs +++ b/crates/solidity/src/yul/parser/statement/expression/function_call/mod.rs @@ -875,16 +875,9 @@ impl FunctionCall { } Name::DataCopy => { let arguments = self.pop_arguments_llvm::(context)?; - let offset = context.builder().build_int_add( - arguments[0].into_int_value(), - context.word_const( - (revive_common::BYTE_LENGTH_X32 + revive_common::BYTE_LENGTH_WORD) as u64, - ), - "datacopy_contract_hash_offset", - )?; revive_llvm_context::polkavm_evm_memory::store( context, - offset, + arguments[0].into_int_value(), arguments[1].into_int_value(), ) .map(|_| None)