Constructors and contract creation (#11)

Implement constructor logic and support create/create2 in the mock runtime

Signed-off-by: xermicus <cyrill@parity.io>
This commit is contained in:
Cyrill Leutwiler
2024-05-22 21:35:32 +02:00
committed by GitHub
parent 42697edc67
commit 06aa289d9b
26 changed files with 692 additions and 720 deletions
Generated
+49 -47
View File
@@ -68,7 +68,7 @@ dependencies = [
"proc-macro-error", "proc-macro-error",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.63", "syn 2.0.65",
"syn-solidity", "syn-solidity",
"tiny-keccak", "tiny-keccak",
] ]
@@ -142,9 +142,9 @@ dependencies = [
[[package]] [[package]]
name = "anyhow" name = "anyhow"
version = "1.0.83" version = "1.0.86"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25bdb32cbbdce2b519a9cd7df3a678443100e265d5e25ca763b7572a5104f5f3" checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
[[package]] [[package]]
name = "ark-ff" name = "ark-ff"
@@ -284,7 +284,7 @@ checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.63", "syn 2.0.65",
] ]
[[package]] [[package]]
@@ -394,9 +394,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.0.97" version = "1.0.98"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "099a5357d84c4c61eb35fc8eafa9a79a902c2f76911e5747ced4e032edd8d9b4" checksum = "41c270e7540d725e65ac7f1b212ac8ce349719624d7bcff99f8e2e488e8cf03f"
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
@@ -473,7 +473,7 @@ dependencies = [
"heck 0.5.0", "heck 0.5.0",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.63", "syn 2.0.65",
] ]
[[package]] [[package]]
@@ -500,9 +500,9 @@ dependencies = [
[[package]] [[package]]
name = "const-hex" name = "const-hex"
version = "1.11.3" version = "1.11.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ba00838774b4ab0233e355d26710fbfc8327a05c017f6dc4873f876d1f79f78" checksum = "70ff96486ccc291d36a958107caf2c0af8c78c0af7d31ae2f35ce055130de1a6"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"cpufeatures", "cpufeatures",
@@ -589,9 +589,9 @@ dependencies = [
[[package]] [[package]]
name = "crossbeam-utils" name = "crossbeam-utils"
version = "0.8.19" version = "0.8.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80"
[[package]] [[package]]
name = "crunchy" name = "crunchy"
@@ -698,9 +698,9 @@ dependencies = [
[[package]] [[package]]
name = "either" name = "either"
version = "1.11.0" version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b"
[[package]] [[package]]
name = "elliptic-curve" name = "elliptic-curve"
@@ -1029,7 +1029,7 @@ dependencies = [
[[package]] [[package]]
name = "inkwell" name = "inkwell"
version = "0.4.0" 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 = [ dependencies = [
"either", "either",
"inkwell_internals", "inkwell_internals",
@@ -1043,11 +1043,11 @@ dependencies = [
[[package]] [[package]]
name = "inkwell_internals" name = "inkwell_internals"
version = "0.9.0" 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 = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.63", "syn 2.0.65",
] ]
[[package]] [[package]]
@@ -1141,9 +1141,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.154" version = "0.2.155"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
[[package]] [[package]]
name = "libm" name = "libm"
@@ -1153,9 +1153,9 @@ checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058"
[[package]] [[package]]
name = "libmimalloc-sys" name = "libmimalloc-sys"
version = "0.1.37" version = "0.1.38"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81eb4061c0582dedea1cbc7aff2240300dd6982e0239d1c99e65c1dbf4a30ba7" checksum = "0e7bb23d733dfcc8af652a78b7bf232f0e967710d044732185e561e47c0336b6"
dependencies = [ dependencies = [
"cc", "cc",
"libc", "libc",
@@ -1163,9 +1163,9 @@ dependencies = [
[[package]] [[package]]
name = "linux-raw-sys" name = "linux-raw-sys"
version = "0.4.13" version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
[[package]] [[package]]
name = "lld-sys" name = "lld-sys"
@@ -1209,9 +1209,9 @@ checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d"
[[package]] [[package]]
name = "mimalloc" name = "mimalloc"
version = "0.1.41" version = "0.1.42"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f41a2280ded0da56c8cf898babb86e8f10651a34adcfff190ae9a1159c6908d" checksum = "e9186d86b79b52f4a77af65604b51225e8db1d6ee7e3f41aec1e40829c71a176"
dependencies = [ dependencies = [
"libmimalloc-sys", "libmimalloc-sys",
] ]
@@ -1315,6 +1315,7 @@ checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575"
name = "pallet-contracts-pvm-llapi" name = "pallet-contracts-pvm-llapi"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow",
"inkwell", "inkwell",
] ]
@@ -1379,9 +1380,9 @@ dependencies = [
[[package]] [[package]]
name = "plotters" name = "plotters"
version = "0.3.5" version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45" checksum = "a15b6eccb8484002195a3e44fe65a4ce8e93a625797a063735536fd59cb01cf3"
dependencies = [ dependencies = [
"num-traits", "num-traits",
"plotters-backend", "plotters-backend",
@@ -1392,15 +1393,15 @@ dependencies = [
[[package]] [[package]]
name = "plotters-backend" name = "plotters-backend"
version = "0.3.5" version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609" checksum = "414cec62c6634ae900ea1c56128dfe87cf63e7caece0852ec76aba307cebadb7"
[[package]] [[package]]
name = "plotters-svg" name = "plotters-svg"
version = "0.3.5" version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab" checksum = "81b30686a7d9c3e010b84284bdd26a29f2138574f52f5eb6f794fc0ad924e705"
dependencies = [ dependencies = [
"plotters-backend", "plotters-backend",
] ]
@@ -1518,9 +1519,9 @@ dependencies = [
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.82" version = "1.0.83"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b" checksum = "0b33eb56c327dec362a9e55b3ad14f9d2f0904fb5a5b03b513ab5465399e9f43"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
@@ -1732,6 +1733,7 @@ dependencies = [
"alloy-sol-types", "alloy-sol-types",
"env_logger", "env_logger",
"hex", "hex",
"log",
"polkavm", "polkavm",
"rayon", "rayon",
"revive-common", "revive-common",
@@ -2026,7 +2028,7 @@ checksum = "6048858004bcff69094cd972ed40a32500f153bd3be9f716b2eed2e8217c4838"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.63", "syn 2.0.65",
] ]
[[package]] [[package]]
@@ -2198,9 +2200,9 @@ dependencies = [
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.63" version = "2.0.65"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf5be731623ca1a1fb7d8be6f261a3be6d3e2337b8a1f97be944d020c8fcb704" checksum = "d2863d96a84c6439701d7a38f9de935ec562c8832cc55d1dde0f513b52fad106"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -2216,7 +2218,7 @@ dependencies = [
"paste", "paste",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.63", "syn 2.0.65",
] ]
[[package]] [[package]]
@@ -2248,22 +2250,22 @@ dependencies = [
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "1.0.60" version = "1.0.61"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "579e9083ca58dd9dcf91a9923bb9054071b9ebbd800b342194c9feb0ee89fc18" checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709"
dependencies = [ dependencies = [
"thiserror-impl", "thiserror-impl",
] ]
[[package]] [[package]]
name = "thiserror-impl" name = "thiserror-impl"
version = "1.0.60" version = "1.0.61"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2470041c06ec3ac1ab38d0356a6119054dedaea53e12fbefc0de730a1c08524" checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.63", "syn 2.0.65",
] ]
[[package]] [[package]]
@@ -2287,9 +2289,9 @@ dependencies = [
[[package]] [[package]]
name = "toml_datetime" name = "toml_datetime"
version = "0.6.5" version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf"
[[package]] [[package]]
name = "toml_edit" name = "toml_edit"
@@ -2414,7 +2416,7 @@ dependencies = [
"once_cell", "once_cell",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.63", "syn 2.0.65",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
@@ -2436,7 +2438,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.63", "syn 2.0.65",
"wasm-bindgen-backend", "wasm-bindgen-backend",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
@@ -2675,7 +2677,7 @@ checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.63", "syn 2.0.65",
] ]
[[package]] [[package]]
@@ -2695,7 +2697,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.63", "syn 2.0.65",
] ]
[[package]] [[package]]
+2 -1
View File
@@ -36,6 +36,7 @@ alloy-sol-types = "0.6"
env_logger = { version = "0.10.0", default-features = false } env_logger = { version = "0.10.0", default-features = false }
serde_stacker = "0.1" serde_stacker = "0.1"
criterion = { version = "0.5", features = ["html_reports"] } criterion = { version = "0.5", features = ["html_reports"] }
log = { version = "0.4" }
# Benchmarking against EVM # Benchmarking against EVM
primitive-types = { version = "0.12", features = ["codec"] } 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] [workspace.dependencies.inkwell]
git = "https://github.com/TheDan64/inkwell.git" git = "https://github.com/TheDan64/inkwell.git"
commit = "d916c66" rev = "6c0fb56b3554e939f9ca61b465043d6a84fb7b95"
default-features = false default-features = false
features = ["serde", "llvm18-0", "no-libffi-linking", "target-riscv"] features = ["serde", "llvm18-0", "no-libffi-linking", "target-riscv"]
+2 -1
View File
@@ -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 - [x] Use PolkaVM allocator for heap space
- [ ] Exercice `schlau` and possibly `smart-bench` benchmark cases - [ ] 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 - [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 - [ ] Calling conventions for calling other contracts
- [ ] Runtime environment isn't fully figured out; implement all EVM builtins - [ ] Runtime environment isn't fully figured out; implement all EVM builtins
- [ ] Iron out many leftovers from the ZKVM target - [ ] 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 - [ ] Document differences from EVM
- [ ] Audit for bugs and correctness - [ ] Audit for bugs and correctness
- [x] Rebranding - [x] Rebranding
- [ ] Remove support for vyper
+1
View File
@@ -11,6 +11,7 @@ alloy-primitives = { workspace = true }
alloy-sol-types = { workspace = true } alloy-sol-types = { workspace = true }
hex = { workspace = true } hex = { workspace = true }
env_logger = { workspace = true } env_logger = { workspace = true }
log = { workspace = true }
revive-solidity = { path = "../solidity" } revive-solidity = { path = "../solidity" }
revive-differential = { path = "../differential" } revive-differential = { path = "../differential" }
+8 -8
View File
@@ -1,10 +1,10 @@
{ {
"Baseline": 2824, "Baseline": 934,
"Computation": 6253, "Computation": 4360,
"DivisionArithmetics": 41341, "DivisionArithmetics": 39448,
"ERC20": 54329, "ERC20": 52461,
"Events": 3642, "Events": 1749,
"FibonacciIterative": 4866, "FibonacciIterative": 2973,
"Flipper": 3270, "Flipper": 3368,
"SHA1": 34605 "SHA1": 32709
} }
+21
View File
@@ -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"}();
}
}
+4
View File
@@ -4,6 +4,10 @@ pragma solidity ^0.8;
contract Flipper { contract Flipper {
bool coin; bool coin;
constructor(bool _coin) {
coin = _coin;
}
function flip() public { function flip() public {
coin = !coin; coin = !coin;
} }
+48 -2
View File
@@ -1,5 +1,5 @@
use alloy_primitives::{I256, U256}; use alloy_primitives::{I256, U256};
use alloy_sol_types::{sol, SolCall}; use alloy_sol_types::{sol, SolCall, SolConstructor};
use crate::mock_runtime::{CallOutput, State}; use crate::mock_runtime::{CallOutput, State};
@@ -13,7 +13,11 @@ pub struct Contract {
sol!(contract Baseline { function baseline() public payable; }); sol!(contract Baseline { function baseline() public payable; });
sol!(contract Flipper { function flip() public; }); sol!(contract Flipper {
constructor (bool);
function flip() public;
});
sol!(contract Computation { sol!(contract Computation {
function odd_product(int32 n) public pure returns (int64); function odd_product(int32 n) public pure returns (int64);
@@ -113,6 +117,12 @@ sol!(
} }
); );
sol!(
contract CreateB {
fallback() external payable;
}
);
impl Contract { impl Contract {
/// Execute the 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 { pub fn erc20() -> Self {
let code = include_str!("../contracts/ERC20.sol"); let code = include_str!("../contracts/ERC20.sol");
let name = "ERC20"; let name = "ERC20";
@@ -359,6 +381,30 @@ impl Contract {
calldata: Events::emitEventCall::new((topics,)).abi_encode(), 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)] #[cfg(test)]
+155 -32
View File
@@ -10,10 +10,10 @@ use revive_llvm_context::polkavm_const::runtime_api;
/// The mocked blockchain account. /// The mocked blockchain account.
#[derive(Debug, Default, Clone)] #[derive(Debug, Default, Clone)]
struct Account { pub struct Account {
value: U256, pub value: U256,
contract: Option<U256>, pub contract: Option<B256>,
storage: HashMap<U256, U256>, pub storage: HashMap<U256, U256>,
} }
/// Emitted event data. /// Emitted event data.
@@ -40,7 +40,7 @@ pub struct CallOutput {
enum Export { enum Export {
#[default] #[default]
Call, Call,
Deploy(U256), Deploy(B256),
} }
/// Possible contract call return flags. /// Possible contract call return flags.
@@ -107,6 +107,8 @@ pub struct Transaction {
} }
impl Transaction { impl Transaction {
pub const CALL_STACK_SIZE: usize = 1024;
pub fn default_address() -> Address { pub fn default_address() -> Address {
Address::default().create2(B256::default(), keccak256([]).0) Address::default().create2(B256::default(), keccak256([]).0)
} }
@@ -129,10 +131,8 @@ impl Transaction {
.unwrap_or_else(|| panic!("callee has no associated account: {account}")) .unwrap_or_else(|| panic!("callee has no associated account: {account}"))
} }
fn create2(&self, salt: U256, blob_hash: U256) -> Address { fn create2(&self, salt: B256, blob_hash: B256) -> Address {
self.top_frame() self.top_frame().callee.create2(salt, blob_hash)
.callee
.create2(salt.to_be_bytes(), blob_hash.to_be_bytes())
} }
} }
@@ -168,10 +168,26 @@ impl TransactionBuilder {
self self
} }
/// Set the transaction to deploy code. /// Helper to setup the transaction for deploy code.
pub fn deploy(mut self, code: &[u8]) -> Self { /// - 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<B256>) -> Self {
let blob_hash = self.context.state.upload_code(code); 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().export = Export::Deploy(blob_hash);
self.context.top_frame_mut().callee = address;
self.context
.state
.create_account(address, Default::default(), blob_hash);
self self
} }
@@ -183,7 +199,7 @@ impl TransactionBuilder {
self.context.state.create_account( self.context.state.create_account(
Transaction::default_address(), Transaction::default_address(),
Default::default(), Default::default(),
keccak256(code).into(), keccak256(code),
); );
self self
} }
@@ -193,12 +209,11 @@ impl TransactionBuilder {
/// Reverts any state changes if the contract reverts or the exuection traps. /// Reverts any state changes if the contract reverts or the exuection traps.
pub fn call(mut self) -> (State, CallOutput) { pub fn call(mut self) -> (State, CallOutput) {
let blob_hash = match self.context.top_frame().export { let blob_hash = match self.context.top_frame().export {
Export::Call => self.context.top_account_mut().contract.unwrap_or_else(|| { Export::Call => self
panic!( .context
"not a contract account: {}", .top_account_mut()
self.context.top_frame().callee .contract
) .expect("balance transfer"),
}),
Export::Deploy(blob_hash) => blob_hash, Export::Deploy(blob_hash) => blob_hash,
}; };
let code = self let code = self
@@ -207,7 +222,12 @@ impl TransactionBuilder {
.blobs .blobs
.get(&blob_hash) .get(&blob_hash)
.unwrap_or_else(|| panic!("contract code not found: {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) self.call_on(&mut instance, export)
} }
@@ -223,14 +243,6 @@ impl TransactionBuilder {
let mut state_args = polkavm::StateArgs::default(); let mut state_args = polkavm::StateArgs::default();
state_args.set_gas(polkavm::Gas::MAX); 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; let callvalue = self.context.top_frame().callvalue;
self.context.top_account_mut().value += callvalue; self.context.top_account_mut().value += callvalue;
@@ -271,7 +283,7 @@ impl From<State> for TransactionBuilder {
/// The mocked blockchain state. /// The mocked blockchain state.
#[derive(Default, Clone, Debug)] #[derive(Default, Clone, Debug)]
pub struct State { pub struct State {
blobs: HashMap<U256, Vec<u8>>, blobs: HashMap<B256, Vec<u8>>,
accounts: HashMap<Address, Account>, accounts: HashMap<Address, Account>,
} }
@@ -279,6 +291,19 @@ impl State {
pub const BLOCK_NUMBER: u64 = 123; pub const BLOCK_NUMBER: u64 = 123;
pub const BLOCK_TIMESTAMP: u64 = 456; 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 { pub fn transaction(self) -> TransactionBuilder {
TransactionBuilder { TransactionBuilder {
state_before: self.clone(), state_before: self.clone(),
@@ -289,8 +314,8 @@ impl State {
} }
} }
pub fn upload_code(&mut self, code: &[u8]) -> U256 { pub fn upload_code(&mut self, code: &[u8]) -> B256 {
let blob_hash = keccak256(code).into(); let blob_hash = keccak256(code);
self.blobs.insert(blob_hash, code.to_vec()); self.blobs.insert(blob_hash, code.to_vec());
blob_hash 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( self.accounts.insert(
address, address,
Account { Account {
@@ -318,6 +343,10 @@ impl State {
}, },
); );
} }
pub fn accounts(&self) -> &HashMap<Address, Account> {
&self.accounts
}
} }
fn link_host_functions(engine: &Engine) -> Linker<Transaction> { fn link_host_functions(engine: &Engine) -> Linker<Transaction> {
@@ -608,6 +637,99 @@ fn link_host_functions(engine: &Engine) -> Linker<Transaction> {
) )
.unwrap(); .unwrap();
linker
.func_wrap(
runtime_api::INSTANTIATE,
|caller: Caller<Transaction>, 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::<Arguments>()];
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 linker
} }
@@ -634,7 +756,8 @@ pub fn instantiate_module(
} }
pub fn prepare(code: &[u8], config: Option<Config>) -> (Instance<Transaction>, ExportIndex) { pub fn prepare(code: &[u8], config: Option<Config>) -> (Instance<Transaction>, 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(); let engine = Engine::new(&config.unwrap_or_default()).unwrap();
+94 -8
View File
@@ -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 alloy_sol_types::{sol, SolCall};
use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
use sha1::Digest; use sha1::Digest;
@@ -27,16 +27,24 @@ fn fibonacci() {
#[test] #[test]
fn flipper() { fn flipper() {
let (state, address) = State::new_deployed(Contract::flipper_constructor(true));
let contract = Contract::flipper(); let contract = Contract::flipper();
let address = Transaction::default_address(); let (state, output) = state
.transaction()
let (state, output) = contract.execute(); .calldata(contract.calldata.clone())
assert_eq!(output.flags, ReturnFlags::Success); .callee(address)
state.assert_storage_key(address, U256::ZERO, U256::from(1)); .call();
let (state, output) = state.transaction().calldata(contract.calldata).call();
assert_eq!(output.flags, ReturnFlags::Success); assert_eq!(output.flags, ReturnFlags::Success);
state.assert_storage_key(address, U256::ZERO, U256::ZERO); 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] #[test]
@@ -421,3 +429,81 @@ fn events() {
assert_success(&Contract::event(U256::ZERO), true); assert_success(&Contract::event(U256::ZERO), true);
assert_success(&Contract::event(U256::from(123)), 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);
}
}
}
+1 -9
View File
@@ -33,9 +33,6 @@ pub static GLOBAL_CALL_FLAGS: &str = "call_flags";
/// The extra ABI data global variable name. /// The extra ABI data global variable name.
pub static GLOBAL_EXTRA_ABI_DATA: &str = "extra_abi_data"; 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. /// The constant array global variable name prefix.
pub static GLOBAL_CONST_ARRAY_PREFIX: &str = "const_array_"; 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; pub const SYSTEM_CALL_BIT: bool = true;
/// The deployer call header size that consists of: /// The deployer call header size that consists of:
/// - selector (4 bytes)
/// - salt (32 bytes)
/// - bytecode hash (32 bytes) /// - bytecode hash (32 bytes)
/// - constructor arguments offset (32 bytes) pub const DEPLOYER_CALL_HEADER_SIZE: usize = revive_common::BYTE_LENGTH_WORD;
/// - constructor arguments length (32 bytes)
pub const DEPLOYER_CALL_HEADER_SIZE: usize =
revive_common::BYTE_LENGTH_X32 + (revive_common::BYTE_LENGTH_WORD * 4);
@@ -24,6 +24,8 @@ pub static HASH_KECCAK_256: &str = "hash_keccak_256";
pub static INPUT: &str = "input"; pub static INPUT: &str = "input";
pub static INSTANTIATE: &str = "instantiate";
pub static NOW: &str = "now"; pub static NOW: &str = "now";
pub static RETURN: &str = "seal_return"; pub static RETURN: &str = "seal_return";
@@ -128,7 +128,7 @@ impl<'ctx> Function<'ctx> {
) { ) {
for attribute_kind in attributes.into_iter() { for attribute_kind in attributes.into_iter() {
match attribute_kind { 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 => { attribute_kind @ Attribute::AlwaysInline if force => {
let is_optimize_none_set = declaration let is_optimize_none_set = declaration
.value .value
@@ -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<D> WriteLLVM<D> for DeployerCall
where
D: Dependency + Clone,
{
fn declare(&mut self, context: &mut Context<D>) -> 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<D>) -> 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(())
}
}
@@ -1,6 +1,7 @@
//! The entry function. //! The entry function.
use inkwell::types::BasicType; use inkwell::types::BasicType;
use inkwell::values::BasicValue;
use crate::polkavm::context::address_space::AddressSpace; use crate::polkavm::context::address_space::AddressSpace;
use crate::polkavm::context::function::runtime::Runtime; use crate::polkavm::context::function::runtime::Runtime;
@@ -8,7 +9,6 @@ use crate::polkavm::context::Context;
use crate::polkavm::r#const::*; use crate::polkavm::r#const::*;
use crate::polkavm::Dependency; use crate::polkavm::Dependency;
use crate::polkavm::WriteLLVM; use crate::polkavm::WriteLLVM;
use crate::PolkaVMPointer as Pointer;
/// The entry function. /// The entry function.
/// The function is a wrapper managing the runtime and deploy code calling logic. /// The function is a wrapper managing the runtime and deploy code calling logic.
@@ -23,8 +23,11 @@ impl Entry {
/// The number of mandatory arguments. /// The number of mandatory arguments.
pub const MANDATORY_ARGUMENTS_COUNT: usize = 2; pub const MANDATORY_ARGUMENTS_COUNT: usize = 2;
/// Reserve 1kb for calldata. /// Reserve 1mb for calldata.
pub const MAX_CALLDATA_SIZE: usize = 1024; pub const MAX_CALLDATA_SIZE: usize = 1024 * 1024;
/// Reserve 1mb for returndata.
pub const MAX_RETURNDATA_SIZE: usize = 1024 * 1024;
/// Initializes the global variables. /// Initializes the global variables.
/// The pointers are not initialized, because it's not possible to create a null pointer. /// 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(), 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( context.set_global(
crate::polkavm::GLOBAL_HEAP_MEMORY_POINTER, crate::polkavm::GLOBAL_HEAP_MEMORY_POINTER,
context.llvm().ptr_type(AddressSpace::Generic.into()), context.llvm().ptr_type(AddressSpace::Generic.into()),
@@ -61,9 +72,9 @@ impl Entry {
); );
context.set_global( context.set_global(
crate::polkavm::GLOBAL_RETURN_DATA_SIZE, crate::polkavm::GLOBAL_RETURN_DATA_SIZE,
context.word_type(), context.xlen_type(),
AddressSpace::Stack, AddressSpace::Stack,
context.word_const(0), context.xlen_type().const_zero().as_basic_value_enum(),
); );
context.set_global( context.set_global(
@@ -162,27 +173,6 @@ impl Entry {
calldata_size_casted, 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(()) Ok(())
} }
@@ -2,7 +2,6 @@
pub mod default_call; pub mod default_call;
pub mod deploy_code; pub mod deploy_code;
pub mod deployer_call;
pub mod entry; pub mod entry;
pub mod runtime_code; pub mod runtime_code;
@@ -13,14 +12,13 @@ use crate::polkavm::Dependency;
use crate::polkavm::WriteLLVM; use crate::polkavm::WriteLLVM;
use self::default_call::DefaultCall; use self::default_call::DefaultCall;
use self::deployer_call::DeployerCall;
/// The front-end runtime functions. /// The front-end runtime functions.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Runtime { pub struct Runtime {
/// The address space where the calldata is allocated. /// The address space where the calldata is allocated.
/// Solidity uses the ordinary heap. Vyper uses the auxiliary heap. /// Solidity uses the ordinary heap. Vyper uses the auxiliary heap.
address_space: AddressSpace, _address_space: AddressSpace,
} }
impl Runtime { impl Runtime {
@@ -34,8 +32,8 @@ impl Runtime {
pub const FUNCTION_RUNTIME_CODE: &'static str = "__runtime"; pub const FUNCTION_RUNTIME_CODE: &'static str = "__runtime";
/// A shortcut constructor. /// A shortcut constructor.
pub fn new(address_space: AddressSpace) -> Self { pub fn new(_address_space: AddressSpace) -> Self {
Self { address_space } Self { _address_space }
} }
/// Returns the corresponding runtime function. /// Returns the corresponding runtime function.
@@ -52,18 +50,6 @@ impl Runtime {
.borrow() .borrow()
.declaration() .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<D> WriteLLVM<D> for Runtime impl<D> WriteLLVM<D> for Runtime
@@ -74,7 +60,6 @@ where
//DefaultCall::new(context.llvm_runtime().far_call).declare(context)?; //DefaultCall::new(context.llvm_runtime().far_call).declare(context)?;
DefaultCall::new(context.llvm_runtime().static_call).declare(context)?; DefaultCall::new(context.llvm_runtime().static_call).declare(context)?;
DefaultCall::new(context.llvm_runtime().delegate_call).declare(context)?; DefaultCall::new(context.llvm_runtime().delegate_call).declare(context)?;
DeployerCall::new(self.address_space).declare(context)?;
Ok(()) Ok(())
} }
@@ -83,7 +68,6 @@ where
//DefaultCall::new(context.llvm_runtime().far_call).into_llvm(context)?; //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().static_call).into_llvm(context)?;
DefaultCall::new(context.llvm_runtime().delegate_call).into_llvm(context)?; DefaultCall::new(context.llvm_runtime().delegate_call).into_llvm(context)?;
DeployerCall::new(self.address_space).into_llvm(context)?;
Ok(()) Ok(())
} }
+5 -16
View File
@@ -136,7 +136,9 @@ where
module: &inkwell::module::Module<'ctx>, module: &inkwell::module::Module<'ctx>,
) { ) {
module 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"); .expect("the PolkaVM guest API module should be linkable");
for export in runtime_api::EXPORTS { for export in runtime_api::EXPORTS {
@@ -164,7 +166,7 @@ where
size: u32, size: u32,
) { ) {
module module
.link_in_module(pallet_contracts_pvm_llapi::min_stack_size( .link_in_module(pallet_contracts_pvm_llapi::polkavm_guest::min_stack_size(
llvm, llvm,
"polkavm_stack_size", "polkavm_stack_size",
size, size,
@@ -1139,26 +1141,13 @@ where
self.builder.build_unreachable().unwrap(); self.builder.build_unreachable().unwrap();
} }
/// Builds a long contract exit sequence. /// Builds a 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( pub fn build_exit(
&self, &self,
flags: inkwell::values::IntValue<'ctx>, flags: inkwell::values::IntValue<'ctx>,
offset: inkwell::values::IntValue<'ctx>, offset: inkwell::values::IntValue<'ctx>,
length: inkwell::values::IntValue<'ctx>, length: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<()> { ) -> 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 offset_truncated = self.safe_truncate_int_to_xlen(offset)?;
let length_truncated = self.safe_truncate_int_to_xlen(length)?; let length_truncated = self.safe_truncate_int_to_xlen(length)?;
let offset_into_heap = self.build_heap_gep(offset_truncated, length_truncated)?; let offset_into_heap = self.build_heap_gep(offset_truncated, length_truncated)?;
+67 -52
View File
@@ -5,9 +5,9 @@ use num::Zero;
use crate::polkavm::context::argument::Argument; use crate::polkavm::context::argument::Argument;
use crate::polkavm::context::code_type::CodeType; use crate::polkavm::context::code_type::CodeType;
use crate::polkavm::context::function::runtime::Runtime;
use crate::polkavm::context::Context; use crate::polkavm::context::Context;
use crate::polkavm::Dependency; use crate::polkavm::Dependency;
use crate::polkavm_const::runtime_api;
/// Translates the contract `create` instruction. /// Translates the contract `create` instruction.
/// The instruction is simulated by a call to a system contract. /// The instruction is simulated by a call to a system contract.
@@ -20,32 +20,10 @@ pub fn create<'ctx, D>(
where where
D: Dependency + Clone, D: Dependency + Clone,
{ {
let signature_hash_string = self::create2(context, value, input_offset, input_length, None)
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)
} }
/// Translates the contract `create2` instruction. /// Translates the contract `create2` instruction.
/// The instruction is simulated by a call to a system contract.
pub fn create2<'ctx, D>( pub fn create2<'ctx, D>(
context: &mut Context<'ctx, D>, context: &mut Context<'ctx, D>,
value: inkwell::values::IntValue<'ctx>, value: inkwell::values::IntValue<'ctx>,
@@ -56,28 +34,72 @@ pub fn create2<'ctx, D>(
where where
D: Dependency + Clone, D: Dependency + Clone,
{ {
let signature_hash_string = let input_offset = context.safe_truncate_int_to_xlen(input_offset)?;
crate::polkavm::utils::keccak256(crate::polkavm::DEPLOYER_SIGNATURE_CREATE2.as_bytes()); let input_length = context.safe_truncate_int_to_xlen(input_length)?;
let signature_hash = context.word_const_str_hex(signature_hash_string.as_str());
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 code_hash_pointer = context.build_heap_gep(input_offset, input_length)?;
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) 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 /// 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)) Ok(Argument::new_with_original(hash_value, hash_string))
} }
/// Translates the deployer call header size instruction, Usually, the header consists of: /// Translates the deploy call header size instruction. the header consists of
/// - the deployer contract method signature /// the hash of the bytecode of the contract whose instance is being created.
/// - 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. /// Represents `datasize` in Yul and `PUSH #[$]` in the EVM legacy assembly.
pub fn header_size<'ctx, D>( pub fn header_size<'ctx, D>(
context: &mut Context<'ctx, D>, context: &mut Context<'ctx, D>,
+7 -62
View File
@@ -1,13 +1,9 @@
//! Translates the transaction return operations. //! 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::context::Context;
use crate::polkavm::Dependency; use crate::polkavm::Dependency;
/// Translates the `return` instruction. /// Translates the `return` instruction.
/// Unlike in EVM, zkSync constructors return the array of contract immutables.
pub fn r#return<'ctx, D>( pub fn r#return<'ctx, D>(
context: &mut Context<'ctx, D>, context: &mut Context<'ctx, D>,
offset: inkwell::values::IntValue<'ctx>, offset: inkwell::values::IntValue<'ctx>,
@@ -16,66 +12,15 @@ pub fn r#return<'ctx, D>(
where where
D: Dependency + Clone, D: Dependency + Clone,
{ {
match context.code_type() { if context.code_type().is_none() {
None => { anyhow::bail!("Return is not available if the contract part is undefined");
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,
)?;
}
} }
Ok(()) context.build_exit(
context.integer_const(crate::polkavm::XLEN, 0),
offset,
length,
)
} }
/// Translates the `revert` instruction. /// Translates the `revert` instruction.
@@ -1,10 +1,7 @@
//! Translates the return data instructions. //! Translates the return data instructions.
use inkwell::types::BasicType;
use inkwell::values::BasicValue; 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::context::Context;
use crate::polkavm::Dependency; use crate::polkavm::Dependency;
@@ -15,13 +12,19 @@ pub fn size<'ctx, D>(
where where
D: Dependency + Clone, D: Dependency + Clone,
{ {
match context.get_global_value(crate::polkavm::GLOBAL_RETURN_DATA_SIZE) { let value = context
Ok(global) => Ok(global), .get_global_value(crate::polkavm::GLOBAL_RETURN_DATA_SIZE)?
Err(_error) => Ok(context.word_const(0).as_basic_value_enum()), .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>( pub fn copy<'ctx, D>(
context: &mut Context<'ctx, D>, context: &mut Context<'ctx, D>,
destination_offset: inkwell::values::IntValue<'ctx>, destination_offset: inkwell::values::IntValue<'ctx>,
@@ -31,57 +34,59 @@ pub fn copy<'ctx, D>(
where where
D: Dependency + Clone, D: Dependency + Clone,
{ {
let error_block = context.append_basic_block("return_data_copy_error_block"); let source_offset = context.safe_truncate_int_to_xlen(source_offset)?;
let join_block = context.append_basic_block("return_data_copy_join_block"); 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 block_copy = context.append_basic_block("copy_block");
let copy_slice_end = let block_trap = context.append_basic_block("trap_block");
context let block_check_out_of_bounds = context.append_basic_block("check_out_of_bounds_block");
.builder() let is_overflow = context.builder().build_int_compare(
.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, inkwell::IntPredicate::UGT,
copy_slice_end, source_offset,
return_data_size, context.builder().build_int_sub(
"return_data_copy_is_out_of_bounds", 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); context.set_basic_block(block_check_out_of_bounds);
crate::polkavm::evm::r#return::revert(context, context.word_const(0), context.word_const(0))?; 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); context.set_basic_block(block_trap);
let destination = Pointer::new_with_offset( context.build_call(context.intrinsics().trap, &[], "invalid_returndata_copy");
context, context.build_unreachable();
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_copy);
context.build_memcpy( context.build_memcpy(
context.intrinsics().memory_copy_from_generic, context.intrinsics().memory_copy_from_generic,
destination, context.build_heap_gep(destination_offset, size)?,
source, 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, size,
"return_data_copy_memcpy_from_return_data", "return_data_copy_memcpy_from_return_data",
)?; )
todo!("Build heap GEP to allocate if necessary")
} }
+2 -1
View File
@@ -7,6 +7,7 @@ pub mod metadata_hash;
pub mod utils; pub mod utils;
pub use self::r#const::*; pub use self::r#const::*;
use self::utils::keccak256;
use crate::debug_config::DebugConfig; use crate::debug_config::DebugConfig;
use crate::optimizer::settings::Settings as OptimizerSettings; use crate::optimizer::settings::Settings as OptimizerSettings;
@@ -56,7 +57,7 @@ pub fn build_assembly_text(
assembly_text.to_owned(), assembly_text.to_owned(),
metadata_hash, metadata_hash,
bytecode.to_owned(), bytecode.to_owned(),
Default::default(), keccak256(bytecode),
)) ))
} }
@@ -4,4 +4,5 @@ version = "0.1.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
anyhow = { workspace = true }
inkwell = { workspace = true, features = ["target-riscv", "no-libffi-linking", "llvm18-0"] } inkwell = { workspace = true, features = ["target-riscv", "no-libffi-linking", "llvm18-0"] }
@@ -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<Self> {
Ok(Self {
pointer: builder.build_alloca(r#type, name)?,
builder,
r#type,
current_field: 0,
})
}
pub fn next<V: BasicValue<'ctx>>(mut self, value: V) -> anyhow::Result<Self> {
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,
)
}
+2 -52
View File
@@ -1,52 +1,2 @@
//! This crate vendors the [PolkaVM][0] C API and provides a LLVM module for interacting pub mod calling_convention;
//! with the `pallet-contracts` runtime API. pub mod polkavm_guest;
//! 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<Module<'context>, 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());
}
}
@@ -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<Module<'context>, 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());
}
}
@@ -875,16 +875,9 @@ impl FunctionCall {
} }
Name::DataCopy => { Name::DataCopy => {
let arguments = self.pop_arguments_llvm::<D, 3>(context)?; let arguments = self.pop_arguments_llvm::<D, 3>(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( revive_llvm_context::polkavm_evm_memory::store(
context, context,
offset, arguments[0].into_int_value(),
arguments[1].into_int_value(), arguments[1].into_int_value(),
) )
.map(|_| None) .map(|_| None)