Compare commits

..

2 Commits

Author SHA1 Message Date
xermicus bec5d60b7c release resolc-0.1.0-dev.9 (#178)
Signed-off-by: xermicus <cyrill@parity.io>
2025-01-29 12:49:54 +01:00
xermicus 3608a5a143 runtime-api: pass call arguments in registers instead of spilling to stack (#174)
Companion to paritytech/polkadot-sdk#7319

Signed-off-by: xermicus <cyrill@parity.io>
2025-01-28 12:45:54 +01:00
30 changed files with 910 additions and 1270 deletions
-1
View File
@@ -87,7 +87,6 @@ jobs:
path: | path: |
${{ env.REVIVE_WASM_INSTALL_DIR }}/resolc.js ${{ env.REVIVE_WASM_INSTALL_DIR }}/resolc.js
${{ env.REVIVE_WASM_INSTALL_DIR }}/resolc.wasm ${{ env.REVIVE_WASM_INSTALL_DIR }}/resolc.wasm
${{ env.REVIVE_WASM_INSTALL_DIR }}/resolc_packed.js
retention-days: 1 retention-days: 1
test-revive-wasm: test-revive-wasm:
+12
View File
@@ -2,6 +2,18 @@
## Unreleased ## Unreleased
## v0.1.0-dev.9
This is a development pre-release.
### Added
### Changed
- Syscalls with more than 6 arguments now pack them into registers.
### Fixed
- Remove reloading of the resolc.js file (fix issue with relative path in web worker)
## v0.1.0-dev.8 ## v0.1.0-dev.8
This is a development pre-release. This is a development pre-release.
Generated
+380 -396
View File
File diff suppressed because it is too large Load Diff
+18 -18
View File
@@ -3,7 +3,7 @@ resolver = "2"
members = ["crates/*"] members = ["crates/*"]
[workspace.package] [workspace.package]
version = "0.1.0-dev.8" version = "0.1.0-dev.9"
authors = [ authors = [
"Cyrill Leutwiler <cyrill@parity.io>", "Cyrill Leutwiler <cyrill@parity.io>",
"Parity Technologies <admin@parity.io>", "Parity Technologies <admin@parity.io>",
@@ -14,29 +14,29 @@ repository = "https://github.com/paritytech/revive"
rust-version = "1.81.0" rust-version = "1.81.0"
[workspace.dependencies] [workspace.dependencies]
revive-benchmarks = { version = "0.1.0-dev.8", path = "crates/benchmarks" } revive-benchmarks = { version = "0.1.0-dev.9", path = "crates/benchmarks" }
revive-builtins = { version = "0.1.0-dev.8", path = "crates/builtins" } revive-builtins = { version = "0.1.0-dev.9", path = "crates/builtins" }
revive-common = { version = "0.1.0-dev.8", path = "crates/common" } revive-common = { version = "0.1.0-dev.9", path = "crates/common" }
revive-differential = { version = "0.1.0-dev.8", path = "crates/differential" } revive-differential = { version = "0.1.0-dev.9", path = "crates/differential" }
revive-integration = { version = "0.1.0-dev.8", path = "crates/integration" } revive-integration = { version = "0.1.0-dev.9", path = "crates/integration" }
revive-linker = { version = "0.1.0-dev.8", path = "crates/linker" } revive-linker = { version = "0.1.0-dev.9", path = "crates/linker" }
lld-sys = { version = "0.1.0-dev.8", path = "crates/lld-sys" } lld-sys = { version = "0.1.0-dev.9", path = "crates/lld-sys" }
revive-llvm-context = { version = "0.1.0-dev.8", path = "crates/llvm-context" } revive-llvm-context = { version = "0.1.0-dev.9", path = "crates/llvm-context" }
revive-runtime-api = { version = "0.1.0-dev.8", path = "crates/runtime-api" } revive-runtime-api = { version = "0.1.0-dev.9", path = "crates/runtime-api" }
revive-runner = { version = "0.1.0-dev.8", path = "crates/runner" } revive-runner = { version = "0.1.0-dev.9", path = "crates/runner" }
revive-solidity = { version = "0.1.0-dev.8", path = "crates/solidity" } revive-solidity = { version = "0.1.0-dev.9", path = "crates/solidity" }
revive-stdlib = { version = "0.1.0-dev.8", path = "crates/stdlib" } revive-stdlib = { version = "0.1.0-dev.9", path = "crates/stdlib" }
revive-build-utils = { version = "0.1.0-dev.8", path = "crates/build-utils" } revive-build-utils = { version = "0.1.0-dev.9", path = "crates/build-utils" }
hex = "0.4.3" hex = "0.4.3"
cc = "1.0" cc = "1.0"
libc = "0.2.169" libc = "0.2.169"
tempfile = "3.8" tempfile = "3.8"
anyhow = "1.0" anyhow = "1.0"
semver = { version = "1.0", features = [ "serde" ] } semver = { version = "1.0", features = ["serde"] }
itertools = "0.14" itertools = "0.14"
serde = { version = "1.0", features = [ "derive" ] } serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "1.0", features = [ "arbitrary_precision" ] } serde_json = { version = "1.0", features = ["arbitrary_precision"] }
regex = "1.10" regex = "1.10"
once_cell = "1.19" once_cell = "1.19"
num = "0.4.3" num = "0.4.3"
@@ -73,7 +73,7 @@ assert_fs = "1.1.2"
# polkadot-sdk and friends # polkadot-sdk and friends
codec = { version = "3.6.12", default-features = false, package = "parity-scale-codec" } codec = { version = "3.6.12", default-features = false, package = "parity-scale-codec" }
scale-info = { version = "2.11.6", default-features = false } scale-info = { version = "2.11.6", default-features = false }
polkadot-sdk = { git = "https://github.com/paritytech/polkadot-sdk", rev = "d62a90c8c729acd98c7e9a5cab9803b8b211ffc5" } polkadot-sdk = { git = "https://github.com/paritytech/polkadot-sdk", rev = "4302f74f7874e6a894578731142a7b310a1449b0" }
# llvm # llvm
[workspace.dependencies.inkwell] [workspace.dependencies.inkwell]
-1
View File
@@ -30,7 +30,6 @@ install-npm:
install-wasm: install-npm install-wasm: install-npm
cargo build --target wasm32-unknown-emscripten -p revive-solidity --release --no-default-features cargo build --target wasm32-unknown-emscripten -p revive-solidity --release --no-default-features
npm run build:package
install-llvm-builder: install-llvm-builder:
cargo install --path crates/llvm-builder cargo install --path crates/llvm-builder
@@ -1339,11 +1339,17 @@ where
self.llvm.custom_width_int_type(bit_length as u32) self.llvm.custom_width_int_type(bit_length as u32)
} }
/// Returns the register witdh sized type. /// Returns the XLEN witdh sized type.
pub fn xlen_type(&self) -> inkwell::types::IntType<'ctx> { pub fn xlen_type(&self) -> inkwell::types::IntType<'ctx> {
self.llvm.custom_width_int_type(crate::polkavm::XLEN as u32) self.llvm.custom_width_int_type(crate::polkavm::XLEN as u32)
} }
/// Returns the PolkaVM native register width sized type.
pub fn register_type(&self) -> inkwell::types::IntType<'ctx> {
self.llvm
.custom_width_int_type(revive_common::BIT_LENGTH_X64 as u32)
}
/// Returns the sentinel pointer value. /// Returns the sentinel pointer value.
pub fn sentinel_pointer(&self) -> Pointer<'ctx> { pub fn sentinel_pointer(&self) -> Pointer<'ctx> {
let sentinel_pointer = self let sentinel_pointer = self
+67 -55
View File
@@ -58,39 +58,48 @@ where
}; };
let flags = context.xlen_type().const_int(flags as u64, false); let flags = context.xlen_type().const_int(flags as u64, false);
let argument_type = revive_runtime_api::calling_convention::call(context.llvm()); let flags_and_callee = revive_runtime_api::calling_convention::pack_hi_lo_reg(
let argument_pointer = context.build_alloca_at_entry(argument_type, "call_arguments");
let arguments = &[
flags.as_basic_value_enum(),
address_pointer.value.as_basic_value_enum(),
context
.integer_const(revive_common::BIT_LENGTH_X64, u64::MAX)
.as_basic_value_enum(),
context
.integer_const(revive_common::BIT_LENGTH_X64, u64::MAX)
.as_basic_value_enum(),
deposit_pointer.value.as_basic_value_enum(),
value_pointer.value.as_basic_value_enum(),
input_pointer.value.as_basic_value_enum(),
input_length.as_basic_value_enum(),
output_pointer.value.as_basic_value_enum(),
output_length_pointer.value.as_basic_value_enum(),
];
revive_runtime_api::calling_convention::spill(
context.builder(), context.builder(),
argument_pointer.value, context.llvm(),
argument_type, flags,
arguments, address_pointer.to_int(context),
"address_and_callee",
)?;
let deposit_and_value = revive_runtime_api::calling_convention::pack_hi_lo_reg(
context.builder(),
context.llvm(),
deposit_pointer.to_int(context),
value_pointer.to_int(context),
"deposit_and_value",
)?;
let input_data = revive_runtime_api::calling_convention::pack_hi_lo_reg(
context.builder(),
context.llvm(),
input_length,
input_pointer.to_int(context),
"input_data",
)?;
let output_data = revive_runtime_api::calling_convention::pack_hi_lo_reg(
context.builder(),
context.llvm(),
output_length_pointer.to_int(context),
output_pointer.to_int(context),
"output_data",
)?; )?;
let name = revive_runtime_api::polkavm_imports::CALL; let name = revive_runtime_api::polkavm_imports::CALL;
let argument_pointer = context.builder().build_ptr_to_int(
argument_pointer.value,
context.xlen_type(),
"call_argument_pointer",
)?;
let success = context let success = context
.build_runtime_call(name, &[argument_pointer.into()]) .build_runtime_call(
name,
&[
flags_and_callee.into(),
context.register_type().const_all_ones().into(),
context.register_type().const_all_ones().into(),
deposit_and_value.into(),
input_data.into(),
output_data.into(),
],
)
.unwrap_or_else(|| panic!("{name} should return a value")) .unwrap_or_else(|| panic!("{name} should return a value"))
.into_int_value(); .into_int_value();
@@ -144,38 +153,41 @@ where
let flags = context.xlen_type().const_int(0u64, false); let flags = context.xlen_type().const_int(0u64, false);
let argument_type = revive_runtime_api::calling_convention::delegate_call(context.llvm()); let flags_and_callee = revive_runtime_api::calling_convention::pack_hi_lo_reg(
let argument_pointer = context.build_alloca_at_entry(argument_type, "delegate_call_arguments");
let arguments = &[
flags.as_basic_value_enum(),
address_pointer.value.as_basic_value_enum(),
context
.integer_const(revive_common::BIT_LENGTH_X64, u64::MAX)
.as_basic_value_enum(),
context
.integer_const(revive_common::BIT_LENGTH_X64, u64::MAX)
.as_basic_value_enum(),
deposit_pointer.value.as_basic_value_enum(),
input_pointer.value.as_basic_value_enum(),
input_length.as_basic_value_enum(),
output_pointer.value.as_basic_value_enum(),
output_length_pointer.value.as_basic_value_enum(),
];
revive_runtime_api::calling_convention::spill(
context.builder(), context.builder(),
argument_pointer.value, context.llvm(),
argument_type, flags,
arguments, address_pointer.to_int(context),
"address_and_callee",
)?;
let input_data = revive_runtime_api::calling_convention::pack_hi_lo_reg(
context.builder(),
context.llvm(),
input_length,
input_pointer.to_int(context),
"input_data",
)?;
let output_data = revive_runtime_api::calling_convention::pack_hi_lo_reg(
context.builder(),
context.llvm(),
output_length_pointer.to_int(context),
output_pointer.to_int(context),
"output_data",
)?; )?;
let name = revive_runtime_api::polkavm_imports::DELEGATE_CALL; let name = revive_runtime_api::polkavm_imports::DELEGATE_CALL;
let argument_pointer = context.builder().build_ptr_to_int(
argument_pointer.value,
context.xlen_type(),
"delegate_call_argument_pointer",
)?;
let success = context let success = context
.build_runtime_call(name, &[argument_pointer.into()]) .build_runtime_call(
name,
&[
flags_and_callee.into(),
context.register_type().const_all_ones().into(),
context.register_type().const_all_ones().into(),
deposit_pointer.to_int(context).into(),
input_data.into(),
output_data.into(),
],
)
.unwrap_or_else(|| panic!("{name} should return a value")) .unwrap_or_else(|| panic!("{name} should return a value"))
.into_int_value(); .into_int_value();
+27 -38
View File
@@ -26,15 +26,6 @@ where
let code_hash_pointer = context.build_heap_gep(input_offset, input_length)?; let code_hash_pointer = context.build_heap_gep(input_offset, input_length)?;
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(),
"input_ptr_parameter_offset",
);
let value_pointer = context.build_alloca_at_entry(context.value_type(), "transferred_value"); let value_pointer = context.build_alloca_at_entry(context.value_type(), "transferred_value");
context.build_store(value_pointer, value)?; context.build_store(value_pointer, value)?;
@@ -56,40 +47,38 @@ where
let deposit_pointer = context.build_alloca_at_entry(context.word_type(), "deposit_pointer"); let deposit_pointer = context.build_alloca_at_entry(context.word_type(), "deposit_pointer");
context.build_store(deposit_pointer, context.word_type().const_all_ones())?; context.build_store(deposit_pointer, context.word_type().const_all_ones())?;
let argument_type = revive_runtime_api::calling_convention::instantiate(context.llvm()); let deposit_and_value = revive_runtime_api::calling_convention::pack_hi_lo_reg(
let argument_pointer = context.build_alloca_at_entry(argument_type, "instantiate_arguments");
let arguments = &[
code_hash_pointer.value.as_basic_value_enum(),
context
.integer_const(revive_common::BIT_LENGTH_X64, u64::MAX)
.as_basic_value_enum(),
context
.integer_const(revive_common::BIT_LENGTH_X64, u64::MAX)
.as_basic_value_enum(),
deposit_pointer.value.as_basic_value_enum(),
value_pointer.value.as_basic_value_enum(),
input_data_pointer.value.as_basic_value_enum(),
input_length.as_basic_value_enum(),
address_pointer.value.as_basic_value_enum(),
context.sentinel_pointer().value.as_basic_value_enum(),
context.sentinel_pointer().value.as_basic_value_enum(),
salt_pointer.value.as_basic_value_enum(),
];
revive_runtime_api::calling_convention::spill(
context.builder(), context.builder(),
argument_pointer.value, context.llvm(),
argument_type, deposit_pointer.to_int(context),
arguments, value_pointer.to_int(context),
"deposit_and_value",
)?;
let input_data = revive_runtime_api::calling_convention::pack_hi_lo_reg(
context.builder(),
context.llvm(),
input_length,
code_hash_pointer.to_int(context),
"input_data",
)?;
let address_and_salt = revive_runtime_api::calling_convention::pack_hi_lo_reg(
context.builder(),
context.llvm(),
address_pointer.to_int(context),
salt_pointer.to_int(context),
"output_data",
)?; )?;
let argument_pointer = context.builder().build_ptr_to_int(
argument_pointer.value,
context.xlen_type(),
"instantiate_argument_pointer",
)?;
context.build_runtime_call( context.build_runtime_call(
revive_runtime_api::polkavm_imports::INSTANTIATE, revive_runtime_api::polkavm_imports::INSTANTIATE,
&[argument_pointer.into()], &[
context.register_type().const_all_ones().into(),
context.register_type().const_all_ones().into(),
deposit_and_value.into(),
input_data.into(),
context.register_type().const_all_ones().into(),
address_and_salt.into(),
],
); );
let address = context.build_byte_swap(context.build_load(address_pointer, "address")?)?; let address = context.build_byte_swap(context.build_load(address_pointer, "address")?)?;
+1
View File
@@ -85,6 +85,7 @@ impl ExtBuilder {
.unwrap(); .unwrap();
pallet_balances::GenesisConfig::<Runtime> { pallet_balances::GenesisConfig::<Runtime> {
balances: self.balance_genesis_config, balances: self.balance_genesis_config,
dev_accounts: None,
} }
.assimilate_storage(&mut t) .assimilate_storage(&mut t)
.unwrap(); .unwrap();
-1
View File
@@ -87,6 +87,5 @@ impl pallet_revive::Config for Runtime {
type UploadOrigin = EnsureSigned<AccountId32>; type UploadOrigin = EnsureSigned<AccountId32>;
type InstantiateOrigin = EnsureSigned<AccountId32>; type InstantiateOrigin = EnsureSigned<AccountId32>;
type CodeHashLockupDepositPercent = CodeHashLockupDepositPercent; type CodeHashLockupDepositPercent = CodeHashLockupDepositPercent;
type Debug = ();
type ChainId = ConstU64<420_420_420>; type ChainId = ConstU64<420_420_420>;
} }
+18 -116
View File
@@ -1,10 +1,4 @@
use inkwell::{ use inkwell::{builder::Builder, context::Context, module::Module, values::IntValue};
builder::Builder,
context::Context,
module::Module,
types::{BasicType, StructType},
values::{BasicValueEnum, PointerValue},
};
/// Creates a module that sets the PolkaVM minimum stack size to [`size`] if linked in. /// Creates a module that sets the PolkaVM minimum stack size to [`size`] if linked in.
pub fn min_stack_size<'context>( pub fn min_stack_size<'context>(
@@ -21,115 +15,23 @@ pub fn min_stack_size<'context>(
module module
} }
/// Helper for building function calls with stack spilled arguments. /// Helper for packing two 32 bit integer values into a 64 bit integer value.
/// - `pointer`: points to a struct of the packed argument struct type pub fn pack_hi_lo_reg<'ctx>(
/// - `type`: the packed argument struct type
/// - `arguments`: a correctly ordered list of the struct field values
pub fn spill<'ctx>(
builder: &Builder<'ctx>, builder: &Builder<'ctx>,
pointer: PointerValue<'ctx>, context: &'ctx Context,
r#type: StructType<'ctx>, hi: IntValue<'ctx>,
arguments: &[BasicValueEnum<'ctx>], lo: IntValue<'ctx>,
) -> anyhow::Result<()> { name: &str,
for index in 0..r#type.get_field_types().len() { ) -> anyhow::Result<IntValue<'ctx>> {
let field_pointer = builder.build_struct_gep( assert_eq!(hi.get_type(), context.i32_type());
r#type, assert_eq!(lo.get_type(), context.i32_type());
pointer,
index as u32,
&format!("spill_parameter_{}", index),
)?;
let field_value = arguments
.get(index)
.ok_or_else(|| anyhow::anyhow!("invalid index {index} for struct type {}", r#type))?;
builder.build_store(field_pointer, *field_value)?;
}
Ok(()) let lo_part = builder.build_int_z_extend(lo, context.i64_type(), &format!("{name}_lo_part"))?;
} let hi_part = builder.build_int_z_extend(hi, context.i64_type(), &format!("{name}_hi_part"))?;
let hi_part_shifted = builder.build_left_shift(
/// Returns a packed struct argument type for the `instantiate` API. hi_part,
pub fn instantiate(context: &Context) -> StructType { context.i64_type().const_int(32, false),
context.struct_type( &format!("{name}_hi_part_shifted"),
&[ )?;
// code_hash_ptr: u32, Ok(builder.build_or(hi_part_shifted, lo_part, name)?)
context.i32_type().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.i32_type().as_basic_type_enum(),
// value_ptr: u32,
context.i32_type().as_basic_type_enum(),
// input_data_ptr: u32,
context.i32_type().as_basic_type_enum(),
// input_data_len: u32,
context.i32_type().as_basic_type_enum(),
// address_ptr: u32,
context.i32_type().as_basic_type_enum(),
// output_ptr: u32,
context.i32_type().as_basic_type_enum(),
// output_len_ptr: u32,
context.i32_type().as_basic_type_enum(),
// salt_ptr: u32,
context.i32_type().as_basic_type_enum(),
],
false,
)
}
/// Returns a packed struct argument type for the `call` API.
pub fn call(context: &Context) -> StructType {
context.struct_type(
&[
// flags: u32,
context.i32_type().as_basic_type_enum(),
// address_ptr:
context.i32_type().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.i32_type().as_basic_type_enum(),
// value_ptr: u32,
context.i32_type().as_basic_type_enum(),
// input_data_ptr: u32,
context.i32_type().as_basic_type_enum(),
// input_data_len: u32,
context.i32_type().as_basic_type_enum(),
// output_ptr: u32,
context.i32_type().as_basic_type_enum(),
// output_len_ptr: u32,
context.i32_type().as_basic_type_enum(),
],
false,
)
}
/// Returns a packed struct argument type for the `delegate_call` API.
pub fn delegate_call(context: &Context) -> StructType {
context.struct_type(
&[
// flags: u32,
context.i32_type().as_basic_type_enum(),
// address_ptr:
context.i32_type().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.i32_type().as_basic_type_enum(),
// input_data_ptr: u32,
context.i32_type().as_basic_type_enum(),
// input_data_len: u32,
context.i32_type().as_basic_type_enum(),
// output_ptr: u32,
context.i32_type().as_basic_type_enum(),
// output_len_ptr: u32,
context.i32_type().as_basic_type_enum(),
],
false,
)
} }
+3 -3
View File
@@ -76,7 +76,7 @@ POLKAVM_IMPORT(void, block_hash, uint32_t, uint32_t)
POLKAVM_IMPORT(void, block_number, uint32_t) POLKAVM_IMPORT(void, block_number, uint32_t)
POLKAVM_IMPORT(uint64_t, call, uint32_t) POLKAVM_IMPORT(uint64_t, call, uint64_t, uint64_t, uint64_t, uint64_t, uint64_t, uint64_t)
POLKAVM_IMPORT(uint64_t, call_data_copy, uint32_t, uint32_t, uint32_t) POLKAVM_IMPORT(uint64_t, call_data_copy, uint32_t, uint32_t, uint32_t)
@@ -92,7 +92,7 @@ POLKAVM_IMPORT(uint64_t, code_size, uint32_t)
POLKAVM_IMPORT(void, code_hash, uint32_t, uint32_t) POLKAVM_IMPORT(void, code_hash, uint32_t, uint32_t)
POLKAVM_IMPORT(uint64_t, delegate_call, uint32_t) POLKAVM_IMPORT(uint64_t, delegate_call, uint64_t, uint64_t, uint64_t, uint32_t, uint64_t, uint64_t)
POLKAVM_IMPORT(void, deposit_event, uint32_t, uint32_t, uint32_t, uint32_t) POLKAVM_IMPORT(void, deposit_event, uint32_t, uint32_t, uint32_t, uint32_t)
@@ -106,7 +106,7 @@ POLKAVM_IMPORT(uint64_t, get_storage, uint32_t, uint32_t, uint32_t, uint32_t, ui
POLKAVM_IMPORT(void, hash_keccak_256, uint32_t, uint32_t, uint32_t) POLKAVM_IMPORT(void, hash_keccak_256, uint32_t, uint32_t, uint32_t)
POLKAVM_IMPORT(uint64_t, instantiate, uint32_t) POLKAVM_IMPORT(uint64_t, instantiate, uint64_t, uint64_t, uint64_t, uint64_t, uint64_t, uint64_t)
POLKAVM_IMPORT(void, now, uint32_t) POLKAVM_IMPORT(void, now, uint32_t)
-63
View File
@@ -1,63 +0,0 @@
const fs = require("fs");
const path = require("path");
const { execSync } = require("child_process");
const { minify } = require("terser");
const RESOLC_WASM_TARGET_DIR = path.join(
__dirname,
"../target/wasm32-unknown-emscripten/release",
);
const RESOLC_WASM = path.join(RESOLC_WASM_TARGET_DIR, "resolc.wasm");
const RESOLC_JS = path.join(RESOLC_WASM_TARGET_DIR, "resolc.js");
const RESOLC_JS_PACKED = path.join(RESOLC_WASM_TARGET_DIR, "resolc_packed.js");
const execShellCommand = (cmd) => {
return execSync(cmd, {
encoding: "utf-8",
maxBuffer: 1024 * 1024 * 100,
}).trim();
};
const wasmBase64 = execShellCommand(
`lz4c --no-frame-crc --best --favor-decSpeed "${RESOLC_WASM}" - | tail -c +8 | base64 -w 0`,
);
const wasmSize = fs.statSync(RESOLC_WASM).size;
const miniLz4 = fs.readFileSync(
path.join(__dirname, "utils/mini-lz4.js"),
"utf-8",
);
const base64DecToArr = fs.readFileSync(
path.join(__dirname, "utils/base64DecToArr.js"),
"utf-8",
);
const resolcJs = fs.readFileSync(RESOLC_JS, "utf-8");
const packedJsContent = `
let moduleArgs = { wasmBinary: (function(source, uncompressedSize) {
${miniLz4}
${base64DecToArr}
return uncompress(base64DecToArr(source), uncompressedSize);
})("${wasmBase64}", ${wasmSize}),
};
${resolcJs}
createRevive = createRevive.bind(null, moduleArgs);
`;
minify(packedJsContent)
.then((minifiedJs) => {
if (minifiedJs.error) {
console.error("Error during minification:", minifiedJs.error);
process.exit(1);
}
fs.writeFileSync(RESOLC_JS_PACKED, minifiedJs.code, "utf-8");
console.log(`Combined script written to ${RESOLC_JS_PACKED}`);
})
.catch((err) => {
console.error("Minification failed:", err);
process.exit(1);
});
+36 -54
View File
@@ -1,16 +1,16 @@
const { test, expect } = require("@playwright/test"); const { test, expect } = require('@playwright/test');
const fs = require("fs"); const fs = require('fs');
const path = require("path"); const path = require('path');
function loadFixture(fixture) { function loadFixture(fixture) {
const fixturePath = path.resolve(__dirname, `../fixtures/${fixture}`); const fixturePath = path.resolve(__dirname, `../fixtures/${fixture}`);
return JSON.parse(fs.readFileSync(fixturePath, "utf-8")); return JSON.parse(fs.readFileSync(fixturePath, 'utf-8'));
} }
async function runWorker(page, input) { async function runWorker(page, input) {
return await page.evaluate((input) => { return await page.evaluate((input) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const worker = new Worker("worker.js"); const worker = new Worker('worker.js');
worker.postMessage(JSON.stringify(input)); worker.postMessage(JSON.stringify(input));
worker.onmessage = (event) => { worker.onmessage = (event) => {
@@ -26,80 +26,62 @@ async function runWorker(page, input) {
}, input); }, input);
} }
test("should successfully compile valid Solidity code in browser", async ({ test('should successfully compile valid Solidity code in browser', async ({ page }) => {
page,
}) => {
await page.goto("http://127.0.0.1:8080"); await page.goto("http://127.0.0.1:8080");
await page.setContent(""); await page.setContent("");
const standardInput = loadFixture("storage.json"); const standardInput = loadFixture('storage.json')
const result = await runWorker(page, standardInput); const result = await runWorker(page, standardInput);
expect(typeof result).toBe("string"); expect(typeof result).toBe('string');
let output = JSON.parse(result); let output = JSON.parse(result);
expect(output).toHaveProperty("contracts"); expect(output).toHaveProperty('contracts');
expect(output.contracts["fixtures/storage.sol"]).toHaveProperty("Storage"); expect(output.contracts['fixtures/storage.sol']).toHaveProperty('Storage');
expect(output.contracts["fixtures/storage.sol"].Storage).toHaveProperty( expect(output.contracts['fixtures/storage.sol'].Storage).toHaveProperty('abi');
"abi", expect(output.contracts['fixtures/storage.sol'].Storage).toHaveProperty('evm');
); expect(output.contracts['fixtures/storage.sol'].Storage.evm).toHaveProperty('bytecode');
expect(output.contracts["fixtures/storage.sol"].Storage).toHaveProperty(
"evm",
);
expect(output.contracts["fixtures/storage.sol"].Storage.evm).toHaveProperty(
"bytecode",
);
}); });
test("should successfully compile large valid Solidity code in browser", async ({ test('should successfully compile large valid Solidity code in browser', async ({ page }) => {
page,
}) => {
await page.goto("http://127.0.0.1:8080"); await page.goto("http://127.0.0.1:8080");
await page.setContent(""); await page.setContent("");
const standardInput = loadFixture("token.json"); const standardInput = loadFixture('token.json')
const result = await runWorker(page, standardInput); const result = await runWorker(page, standardInput);
expect(typeof result).toBe("string"); expect(typeof result).toBe('string');
let output = JSON.parse(result); let output = JSON.parse(result);
expect(output).toHaveProperty("contracts"); expect(output).toHaveProperty('contracts');
expect(output.contracts["fixtures/token.sol"]).toHaveProperty("MyToken"); expect(output.contracts['fixtures/token.sol']).toHaveProperty('MyToken');
expect(output.contracts["fixtures/token.sol"].MyToken).toHaveProperty("abi"); expect(output.contracts['fixtures/token.sol'].MyToken).toHaveProperty('abi');
expect(output.contracts["fixtures/token.sol"].MyToken).toHaveProperty("evm"); expect(output.contracts['fixtures/token.sol'].MyToken).toHaveProperty('evm');
expect(output.contracts["fixtures/token.sol"].MyToken.evm).toHaveProperty( expect(output.contracts['fixtures/token.sol'].MyToken.evm).toHaveProperty('bytecode');
"bytecode",
);
}); });
test("should throw an error for invalid Solidity code in browser", async ({ test('should throw an error for invalid Solidity code in browser', async ({ page }) => {
page,
}) => {
await page.goto("http://127.0.0.1:8080"); await page.goto("http://127.0.0.1:8080");
await page.setContent(""); await page.setContent("");
const standardInput = loadFixture("invalid_contract_content.json"); const standardInput = loadFixture('invalid_contract_content.json')
const result = await runWorker(page, standardInput); const result = await runWorker(page, standardInput);
expect(typeof result).toBe("string"); expect(typeof result).toBe('string');
let output = JSON.parse(result); let output = JSON.parse(result);
expect(output).toHaveProperty("errors"); expect(output).toHaveProperty('errors');
expect(Array.isArray(output.errors)).toBeTruthy(); // Check if it's an array expect(Array.isArray(output.errors)).toBeTruthy(); // Check if it's an array
expect(output.errors.length).toBeGreaterThan(0); expect(output.errors.length).toBeGreaterThan(0);
expect(output.errors[0]).toHaveProperty("type"); expect(output.errors[0]).toHaveProperty('type');
expect(output.errors[0].type).toContain("ParserError"); expect(output.errors[0].type).toContain('ParserError');
}); });
test("should return not found error for missing imports in browser", async ({ test('should return not found error for missing imports in browser', async ({page}) => {
page,
}) => {
await page.goto("http://127.0.0.1:8080"); await page.goto("http://127.0.0.1:8080");
await page.setContent(""); await page.setContent("");
const standardInput = loadFixture("missing_import.json"); const standardInput = loadFixture('missing_import.json')
const result = await runWorker(page, standardInput); const result = await runWorker(page, standardInput);
expect(typeof result).toBe("string"); expect(typeof result).toBe('string');
let output = JSON.parse(result); let output = JSON.parse(result);
expect(output).toHaveProperty("errors"); expect(output).toHaveProperty('errors');
expect(Array.isArray(output.errors)).toBeTruthy(); // Check if it's an array expect(Array.isArray(output.errors)).toBeTruthy(); // Check if it's an array
expect(output.errors.length).toBeGreaterThan(0); expect(output.errors.length).toBeGreaterThan(0);
expect(output.errors[0]).toHaveProperty("message"); expect(output.errors[0]).toHaveProperty('message');
expect(output.errors[0].message).toContain( expect(output.errors[0].message).toContain('Source "nonexistent/console.sol" not found');
'Source "nonexistent/console.sol" not found',
);
}); });
+52 -55
View File
@@ -1,57 +1,54 @@
Module.stdinData = null; var Module = {
Module.stdinDataPosition = 0; stdinData: null,
Module.stdoutData = []; stdinDataPosition: 0,
Module.stderrData = []; stdoutData: [],
stderrData: [],
// Method to read all collected stdout data // Function to read and return all collected stdout data as a string
Module.readFromStdout = function () { readFromStdout: function() {
if (!Module.stdoutData.length) return ""; if (!this.stdoutData.length) return "";
const decoder = new TextDecoder("utf-8"); const decoder = new TextDecoder('utf-8');
const data = decoder.decode(new Uint8Array(Module.stdoutData)); const data = decoder.decode(new Uint8Array(this.stdoutData));
Module.stdoutData = []; this.stdoutData = [];
return data; return data;
},
// Function to read and return all collected stderr data as a string
readFromStderr: function() {
if (!this.stderrData.length) return "";
const decoder = new TextDecoder('utf-8');
const data = decoder.decode(new Uint8Array(this.stderrData));
this.stderrData = [];
return data;
},
// Function to set input data for stdin
writeToStdin: function(data) {
const encoder = new TextEncoder();
this.stdinData = encoder.encode(data);
this.stdinDataPosition = 0;
},
// `preRun` is called before the program starts running
preRun: function() {
// Define a custom stdin function
function customStdin() {
if (!Module.stdinData || Module.stdinDataPosition >= Module.stdinData.length) {
return null; // End of input (EOF)
}
return Module.stdinData[Module.stdinDataPosition++];
}
// Define a custom stdout function
function customStdout(char) {
Module.stdoutData.push(char);
}
// Define a custom stderr function
function customStderr(char) {
Module.stderrData.push(char);
}
FS.init(customStdin, customStdout, customStderr);
},
}; };
// Method to read all collected stderr data
Module.readFromStderr = function () {
if (!Module.stderrData.length) return "";
const decoder = new TextDecoder("utf-8");
const data = decoder.decode(new Uint8Array(Module.stderrData));
Module.stderrData = [];
return data;
};
// Method to write data to stdin
Module.writeToStdin = function (data) {
const encoder = new TextEncoder();
Module.stdinData = encoder.encode(data);
Module.stdinDataPosition = 0;
};
// Override the `preRun` method to customize file system initialization
Module.preRun = Module.preRun || [];
Module.preRun.push(function () {
// Custom stdin function
function customStdin() {
if (
!Module.stdinData ||
Module.stdinDataPosition >= Module.stdinData.length
) {
return null; // End of input (EOF)
}
return Module.stdinData[Module.stdinDataPosition++];
}
// Custom stdout function
function customStdout(char) {
Module.stdoutData.push(char);
}
// Custom stderr function
function customStderr(char) {
Module.stderrData.push(char);
}
// Initialize the FS (File System) with custom handlers
FS.init(customStdin, customStdout, customStderr);
});
+25 -30
View File
@@ -1,34 +1,29 @@
mergeInto(LibraryManager.library, { mergeInto(LibraryManager.library, {
soljson_compile: function (inputPtr, inputLen) { soljson_compile: function(inputPtr, inputLen) {
const inputJson = UTF8ToString(inputPtr, inputLen); const inputJson = UTF8ToString(inputPtr, inputLen);
const output = Module.soljson.cwrap("solidity_compile", "string", [ const output = Module.soljson.cwrap('solidity_compile', 'string', ['string'])(inputJson);
"string", return stringToNewUTF8(output);
])(inputJson); },
return stringToNewUTF8(output); soljson_version: function() {
}, const version = Module.soljson.cwrap("solidity_version", "string", [])();
soljson_version: function () { return stringToNewUTF8(version);
const version = Module.soljson.cwrap("solidity_version", "string", [])(); },
return stringToNewUTF8(version); resolc_compile: function(inputPtr, inputLen) {
}, const inputJson = UTF8ToString(inputPtr, inputLen);
resolc_compile: function (inputPtr, inputLen) { var revive = createRevive();
const inputJson = UTF8ToString(inputPtr, inputLen); revive.writeToStdin(inputJson);
var revive = createRevive();
revive.writeToStdin(inputJson);
// Call main on the new instance // Call main on the new instance
const result = revive.callMain(["--recursive-process"]); const result = revive.callMain(['--recursive-process']);
if (result) { if (result) {
const stderrString = revive.readFromStderr(); const stderrString = revive.readFromStderr();
const error = JSON.stringify({ const error = JSON.stringify({ type: 'error', message: stderrString || "Unknown error" });
type: "error", return stringToNewUTF8(error);
message: stderrString || "Unknown error", } else {
}); const stdoutString = revive.readFromStdout();
return stringToNewUTF8(error); const json = JSON.stringify({ type: 'success', data: stdoutString });
} else { return stringToNewUTF8(json);
const stdoutString = revive.readFromStdout(); }
const json = JSON.stringify({ type: "success", data: stdoutString }); },
return stringToNewUTF8(json);
}
},
}); });
+5 -5
View File
@@ -1,9 +1,9 @@
const soljson = require("solc/soljson"); const soljson = require('solc/soljson');
const createRevive = require("./resolc.js"); const createRevive = require('./resolc.js');
async function compile(standardJsonInput) { async function compile(standardJsonInput) {
if (!standardJsonInput) { if (!standardJsonInput) {
throw new Error("Input JSON for the Solidity compiler is required."); throw new Error('Input JSON for the Solidity compiler is required.');
} }
// Initialize the compiler // Initialize the compiler
@@ -14,7 +14,7 @@ async function compile(standardJsonInput) {
compiler.writeToStdin(JSON.stringify(standardJsonInput)); compiler.writeToStdin(JSON.stringify(standardJsonInput));
// Run the compiler // Run the compiler
compiler.callMain(["--standard-json"]); compiler.callMain(['--standard-json']);
// Collect output // Collect output
const stdout = compiler.readFromStdout(); const stdout = compiler.readFromStdout();
@@ -29,4 +29,4 @@ async function compile(standardJsonInput) {
return stdout; return stdout;
} }
module.exports = { compile }; module.exports = { compile };
+20 -20
View File
@@ -1,10 +1,10 @@
const { compile } = require("./revive.js"); const { compile } = require('./revive.js');
const compilerStandardJsonInput = { const compilerStandardJsonInput = {
language: "Solidity", language: 'Solidity',
sources: { sources: {
"MyContract.sol": { 'MyContract.sol': {
content: ` content: `
// SPDX-License-Identifier: UNLICENSED // SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0; pragma solidity ^0.8.0;
contract MyContract { contract MyContract {
@@ -13,26 +13,26 @@ const compilerStandardJsonInput = {
} }
} }
`, `,
},
},
settings: {
optimizer: {
enabled: true,
runs: 200,
},
outputSelection: {
"*": {
"*": ["abi"],
}, },
}, },
}, settings: {
}; optimizer: {
enabled: true,
runs: 200,
},
outputSelection: {
'*': {
'*': ['abi'],
},
},
},
};
async function runCompiler() { async function runCompiler() {
let output = await compile(compilerStandardJsonInput); let output = await compile(compilerStandardJsonInput)
console.log("Output: " + output); console.log("Output: " + output);
} }
runCompiler().catch((err) => { runCompiler().catch(err => {
console.error("Error:", err); console.error('Error:', err);
}); });
+47 -49
View File
@@ -1,53 +1,51 @@
<!doctype html> <!DOCTYPE html>
<html> <html>
<head>
<meta charset="utf-8" />
<title>Web Worker Example</title>
<style>
/* Ensure the pre tag wraps long lines */
pre {
white-space: pre-wrap; /* Wrap long lines */
word-wrap: break-word; /* Break long words */
max-width: 100%; /* Optional: Ensures it doesn't overflow container */
overflow-wrap: break-word; /* Another method for wrapping */
}
</style>
</head>
<body> <head>
<h1>Revive Compilation Output</h1> <meta charset="utf-8" />
<pre id="output"></pre> <title>Web Worker Example</title>
<script> <style>
var outputElement = document.getElementById("output"); /* Ensure the pre tag wraps long lines */
var worker = new Worker("./worker.js"); pre {
const standardJsonInput = { white-space: pre-wrap; /* Wrap long lines */
language: "Solidity", word-wrap: break-word; /* Break long words */
sources: { max-width: 100%; /* Optional: Ensures it doesn't overflow container */
contract: { overflow-wrap: break-word; /* Another method for wrapping */
content: "contract MyContract { function f() public { } }", }
}, </style>
}, </head>
settings: {
optimizer: { <body>
enabled: true, <h1>Revive Compilation Output</h1>
runs: 200, <pre id="output"></pre>
}, <script>
outputSelection: { var outputElement = document.getElementById('output');
"*": { var worker = new Worker('./worker.js');
"*": ["abi"], const standardJsonInput = {
}, language: 'Solidity',
}, sources: {
}, contract: {
}; content: 'contract MyContract { function f() public { } }',
worker.addEventListener( }
"message", },
function (e) { settings: {
outputElement.textContent = e.data.output; optimizer: {
}, enabled: true,
false, runs: 200,
); },
outputSelection: {
'*': {
'*': ['abi'],
}
}
}
};
worker.addEventListener('message', function (e) {
outputElement.textContent = e.data.output;
}, false);
worker.postMessage(JSON.stringify(standardJsonInput));
</script>
</body>
worker.postMessage(JSON.stringify(standardJsonInput));
</script>
</body>
</html> </html>
+11 -9
View File
@@ -1,16 +1,18 @@
importScripts("./soljson.js");
importScripts("./resolc.js"); importScripts('./soljson.js');
importScripts('./resolc.js');
// Handle messages from the main thread // Handle messages from the main thread
onmessage = async function (e) { onmessage = async function (e) {
const m = createRevive(); const m = createRevive();
m.soljson = Module;
// Set input data for stdin m.soljson = Module;
m.writeToStdin(e.data);
// Compile the Solidity source code // Set input data for stdin
m.callMain(["--standard-json"]); m.writeToStdin(e.data);
postMessage({ output: m.readFromStdout() || m.readFromStderr() }); // Compile the Solidity source code
m.callMain(['--standard-json']);
postMessage({output: m.readFromStdout() || m.readFromStderr()});
}; };
+17 -14
View File
@@ -1,19 +1,22 @@
{ {
"language": "Solidity", "language": "Solidity",
"sources": { "sources": {
"fixtures/storage.sol": { "fixtures/storage.sol": {
"content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.8.0;\nimport \"nonexistent/console.sol\";\ncontract MyContract { function greet() public pure returns (string memory) { return \"Hello\" // Missing semicolon }}" "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.8.0;\nimport \"nonexistent/console.sol\";\ncontract MyContract { function greet() public pure returns (string memory) { return \"Hello\" // Missing semicolon }}"
} }
},
"settings": {
"optimizer": {
"enabled": true,
"runs": 200
}, },
"outputSelection": { "settings": {
"*": { "optimizer": {
"*": ["abi"] "enabled": true,
"runs": 200
},
"outputSelection": {
"*": {
"*": [
"abi"
]
}
} }
} }
} }
}
+17 -14
View File
@@ -1,19 +1,22 @@
{ {
"language": "Solidity", "language": "Solidity",
"sources": { "sources": {
"fixtures/storage.sol": { "fixtures/storage.sol": {
"content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.8.0;\nimport \"nonexistent/console.sol\";\ncontract MyContract { function f() public { } }" "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.8.0;\nimport \"nonexistent/console.sol\";\ncontract MyContract { function f() public { } }"
} }
},
"settings": {
"optimizer": {
"enabled": true,
"runs": 200
}, },
"outputSelection": { "settings": {
"*": { "optimizer": {
"*": ["abi"] "enabled": true,
"runs": 200
},
"outputSelection": {
"*": {
"*": [
"abi"
]
}
} }
} }
} }
}
+17 -14
View File
@@ -1,19 +1,22 @@
{ {
"language": "Solidity", "language": "Solidity",
"sources": { "sources": {
"fixtures/storage.sol": { "fixtures/storage.sol": {
"content": "// SPDX-License-Identifier: GPL-3.0\npragma solidity >=0.8.2 <0.9.0;\ncontract Storage {\n uint256 number;\n function store(uint256 num) public { number = num; }\n function retrieve() public view returns (uint256){ return number; }\n}" "content": "// SPDX-License-Identifier: GPL-3.0\npragma solidity >=0.8.2 <0.9.0;\ncontract Storage {\n uint256 number;\n function store(uint256 num) public { number = num; }\n function retrieve() public view returns (uint256){ return number; }\n}"
} }
},
"settings": {
"optimizer": {
"enabled": true,
"runs": 200
}, },
"outputSelection": { "settings": {
"*": { "optimizer": {
"*": ["abi"] "enabled": true,
"runs": 200
},
"outputSelection": {
"*": {
"*": [
"abi"
]
}
} }
} }
} }
}
+77 -74
View File
File diff suppressed because one or more lines are too long
+2 -6
View File
@@ -10,16 +10,12 @@
"example:node": "node ./examples/node/run_revive.js", "example:node": "node ./examples/node/run_revive.js",
"test:node": "mocha --timeout 60000 ./tests", "test:node": "mocha --timeout 60000 ./tests",
"test:bun": "bun test --timeout 60000 node.test", "test:bun": "bun test --timeout 60000 node.test",
"test:all": "npm run test:node && npm run test:bun", "test:all": "npm run test:node && npm run test:bun"
"format": "prettier --write .",
"build:package": "node ./build.js"
}, },
"devDependencies": { "devDependencies": {
"@playwright/test": "^1.49.1", "@playwright/test": "^1.49.1",
"chai": "^5.1.2", "chai": "^5.1.2",
"http-server": "^14.1.1", "http-server": "^14.1.1",
"mocha": "^11.0.1", "mocha": "^11.0.1"
"prettier": "^3.4.2",
"terser": "^5.37.0"
} }
} }
+15 -14
View File
@@ -1,10 +1,10 @@
const { defineConfig, devices } = require("@playwright/test"); const { defineConfig, devices } = require('@playwright/test');
/** /**
* @see https://playwright.dev/docs/test-configuration * @see https://playwright.dev/docs/test-configuration
*/ */
module.exports = defineConfig({ module.exports = defineConfig({
testDir: "./e2e", testDir: './e2e',
/* Run tests in files in parallel */ /* Run tests in files in parallel */
fullyParallel: true, fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */ /* Fail the build on CI if you accidentally left test.only in the source code. */
@@ -14,38 +14,39 @@ module.exports = defineConfig({
/* Opt out of parallel tests on CI. */ /* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined, workers: process.env.CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */ /* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: "list", reporter: 'list',
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: { use: {
/* Base URL to use in actions like `await page.goto('/')`. */ /* Base URL to use in actions like `await page.goto('/')`. */
baseURL: "http://127.0.0.1:8080", baseURL: 'http://127.0.0.1:8080',
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: "on-first-retry", trace: 'on-first-retry',
}, },
timeout: 480000, timeout: 480000,
/* Configure projects for major browsers */ /* Configure projects for major browsers */
projects: [ projects: [
{ {
name: "chromium", name: 'chromium',
use: { ...devices["Desktop Chrome"] }, use: { ...devices['Desktop Chrome'] },
}, },
{ {
name: "firefox", name: 'firefox',
use: { ...devices["Desktop Firefox"] }, use: { ...devices['Desktop Firefox'] },
}, },
{ {
name: "webkit", name: 'webkit',
use: { ...devices["Desktop Safari"] }, use: { ...devices['Desktop Safari'] },
}, }
], ],
/* Run your local dev server before starting the tests */ /* Run your local dev server before starting the tests */
webServer: { webServer: {
command: "npm run example:web", command: 'npm run example:web',
url: "http://127.0.0.1:8080", url: 'http://127.0.0.1:8080',
reuseExistingServer: !process.env.CI, reuseExistingServer: !process.env.CI,
}, },
}); });
+35 -53
View File
@@ -1,88 +1,70 @@
import { expect } from "chai"; import { expect } from 'chai';
import { compile } from "../examples/node/revive.js"; import { compile } from '../examples/node/revive.js';
import { fileURLToPath } from "url"; import { fileURLToPath } from 'url';
import path from "path"; import path from 'path';
import fs from "fs"; import fs from 'fs';
const __filename = fileURLToPath(import.meta.url); const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename); const __dirname = path.dirname(__filename);
function loadFixture(fixture) { function loadFixture(fixture) {
const fixturePath = path.resolve(__dirname, `../fixtures/${fixture}`); const fixturePath = path.resolve(__dirname, `../fixtures/${fixture}`);
return JSON.parse(fs.readFileSync(fixturePath, "utf-8")); return JSON.parse(fs.readFileSync(fixturePath, 'utf-8'));
} }
describe("Compile Function Tests", function () { describe('Compile Function Tests', function () {
it("should successfully compile valid Solidity code", async function () { it('should successfully compile valid Solidity code', async function () {
const standardInput = loadFixture("storage.json"); const standardInput = loadFixture('storage.json')
const result = await compile(standardInput); const result = await compile(standardInput);
expect(result).to.be.a("string"); expect(result).to.be.a('string');
const output = JSON.parse(result); const output = JSON.parse(result);
expect(output).to.have.property("contracts"); expect(output).to.have.property('contracts');
expect(output.contracts["fixtures/storage.sol"]).to.have.property( expect(output.contracts['fixtures/storage.sol']).to.have.property('Storage');
"Storage", expect(output.contracts['fixtures/storage.sol'].Storage).to.have.property('abi');
); expect(output.contracts['fixtures/storage.sol'].Storage).to.have.property('evm');
expect(output.contracts["fixtures/storage.sol"].Storage).to.have.property( expect(output.contracts['fixtures/storage.sol'].Storage.evm).to.have.property('bytecode');
"abi",
);
expect(output.contracts["fixtures/storage.sol"].Storage).to.have.property(
"evm",
);
expect(
output.contracts["fixtures/storage.sol"].Storage.evm,
).to.have.property("bytecode");
}); });
if (typeof globalThis.Bun == "undefined") { if (typeof globalThis.Bun == 'undefined') {
// Running this test with Bun on a Linux host causes: // Running this test with Bun on a Linux host causes:
// RuntimeError: Out of bounds memory access (evaluating 'getWasmTableEntry(index)(a1, a2, a3, a4, a5)') // RuntimeError: Out of bounds memory access (evaluating 'getWasmTableEntry(index)(a1, a2, a3, a4, a5)')
// Once this issue is resolved, the test will be re-enabled. // Once this issue is resolved, the test will be re-enabled.
it("should successfully compile large Solidity code", async function () { it('should successfully compile large Solidity code', async function () {
const standardInput = loadFixture("token.json"); const standardInput = loadFixture('token.json')
const result = await compile(standardInput); const result = await compile(standardInput);
expect(result).to.be.a("string"); expect(result).to.be.a('string');
const output = JSON.parse(result); const output = JSON.parse(result);
expect(output).to.have.property("contracts"); expect(output).to.have.property('contracts');
expect(output.contracts["fixtures/token.sol"]).to.have.property( expect(output.contracts['fixtures/token.sol']).to.have.property('MyToken');
"MyToken", expect(output.contracts['fixtures/token.sol'].MyToken).to.have.property('abi');
); expect(output.contracts['fixtures/token.sol'].MyToken).to.have.property('evm');
expect(output.contracts["fixtures/token.sol"].MyToken).to.have.property( expect(output.contracts['fixtures/token.sol'].MyToken.evm).to.have.property('bytecode');
"abi",
);
expect(output.contracts["fixtures/token.sol"].MyToken).to.have.property(
"evm",
);
expect(
output.contracts["fixtures/token.sol"].MyToken.evm,
).to.have.property("bytecode");
}); });
} }
it("should throw an error for invalid Solidity code", async function () { it('should throw an error for invalid Solidity code', async function () {
const standardInput = loadFixture("invalid_contract_content.json"); const standardInput = loadFixture('invalid_contract_content.json')
const result = await compile(standardInput); const result = await compile(standardInput);
expect(result).to.be.a("string"); expect(result).to.be.a('string');
const output = JSON.parse(result); const output = JSON.parse(result);
expect(output).to.have.property("errors"); expect(output).to.have.property('errors');
expect(output.errors).to.be.an("array"); expect(output.errors).to.be.an('array');
expect(output.errors.length).to.be.greaterThan(0); expect(output.errors.length).to.be.greaterThan(0);
expect(output.errors[0].type).to.exist; expect(output.errors[0].type).to.exist;
expect(output.errors[0].type).to.contain("ParserError"); expect(output.errors[0].type).to.contain("ParserError");
}); });
it("should return not found error for missing imports", async function () { it('should return not found error for missing imports', async function () {
const standardInput = loadFixture("missing_import.json"); const standardInput = loadFixture('missing_import.json')
const result = await compile(standardInput); const result = await compile(standardInput);
const output = JSON.parse(result); const output = JSON.parse(result);
expect(output).to.have.property("errors"); expect(output).to.have.property('errors');
expect(output.errors).to.be.an("array"); expect(output.errors).to.be.an('array');
expect(output.errors.length).to.be.greaterThan(0); expect(output.errors.length).to.be.greaterThan(0);
expect(output.errors[0].message).to.exist; expect(output.errors[0].message).to.exist;
expect(output.errors[0].message).to.include( expect(output.errors[0].message).to.include('Source "nonexistent/console.sol" not found');
'Source "nonexistent/console.sol" not found',
);
}); });
}); });
-46
View File
@@ -1,46 +0,0 @@
function base64DecToArr (sBase64) {
/*\
|*|
|*| Base64 / binary data / UTF-8 strings utilities
|*|
|*| https://developer.mozilla.org/en-US/docs/Web/JavaScript/Base64_encoding_and_decoding
|*|
\*/
/* Array of bytes to Base64 string decoding */
function b64ToUint6 (nChr) {
return nChr > 64 && nChr < 91 ?
nChr - 65
: nChr > 96 && nChr < 123 ?
nChr - 71
: nChr > 47 && nChr < 58 ?
nChr + 4
: nChr === 43 ?
62
: nChr === 47 ?
63
:
0;
}
var
nInLen = sBase64.length,
nOutLen = nInLen * 3 + 1 >> 2, taBytes = new Uint8Array(nOutLen);
for (var nMod3, nMod4, nUint24 = 0, nOutIdx = 0, nInIdx = 0; nInIdx < nInLen; nInIdx++) {
nMod4 = nInIdx & 3;
nUint24 |= b64ToUint6(sBase64.charCodeAt(nInIdx)) << 6 * (3 - nMod4);
if (nMod4 === 3 || nInLen - nInIdx === 1) {
for (nMod3 = 0; nMod3 < 3 && nOutIdx < nOutLen; nMod3++, nOutIdx++) {
taBytes[nOutIdx] = nUint24 >>> (16 >>> nMod3 & 24) & 255;
}
nUint24 = 0;
}
}
return taBytes;
}
-118
View File
@@ -1,118 +0,0 @@
function uncompress(source, uncompressedSize) {
/*
Source https://github.com/ethereum/solidity/blob/develop/scripts/ci/mini-lz4.js
====
based off https://github.com/emscripten-core/emscripten/blob/main/third_party/mini-lz4.js
The license only applies to the body of this function (``uncompress``).
====
MiniLZ4: Minimal LZ4 block decoding and encoding.
based off of node-lz4, https://github.com/pierrec/node-lz4
====
Copyright (c) 2012 Pierre Curto
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
====
changes have the same license
*/
/**
* Decode a block. Assumptions: input contains all sequences of a
* chunk, output is large enough to receive the decoded data.
* If the output buffer is too small, an error will be thrown.
* If the returned value is negative, an error occurred at the returned offset.
*
* @param {ArrayBufferView} input input data
* @param {ArrayBufferView} output output data
* @param {number=} sIdx
* @param {number=} eIdx
* @return {number} number of decoded bytes
* @private
*/
function uncompressBlock (input, output, sIdx, eIdx) {
sIdx = sIdx || 0
eIdx = eIdx || (input.length - sIdx)
// Process each sequence in the incoming data
for (var i = sIdx, n = eIdx, j = 0; i < n;) {
var token = input[i++]
// Literals
var literals_length = (token >> 4)
if (literals_length > 0) {
// length of literals
var l = literals_length + 240
while (l === 255) {
l = input[i++]
literals_length += l
}
// Copy the literals
var end = i + literals_length
while (i < end) output[j++] = input[i++]
// End of buffer?
if (i === n) return j
}
// Match copy
// 2 bytes offset (little endian)
var offset = input[i++] | (input[i++] << 8)
// XXX 0 is an invalid offset value
if (offset === 0) return j
if (offset > j) return -(i-2)
// length of match copy
var match_length = (token & 0xf)
var l = match_length + 240
while (l === 255) {
l = input[i++]
match_length += l
}
// Copy the match
var pos = j - offset // position of the match copy in the current output
var end = j + match_length + 4 // minmatch = 4
while (j < end) output[j++] = output[pos++]
}
return j
}
var result = new ArrayBuffer(uncompressedSize);
var sourceIndex = 0;
var destIndex = 0;
var blockSize;
while((blockSize = (source[sourceIndex] | (source[sourceIndex + 1] << 8) | (source[sourceIndex + 2] << 16) | (source[sourceIndex + 3] << 24))) > 0)
{
sourceIndex += 4;
if (blockSize & 0x80000000)
{
blockSize &= 0x7FFFFFFFF;
for (var i = 0; i < blockSize; i++) {
result[destIndex++] = source[sourceIndex++];
}
}
else
{
destIndex += uncompressBlock(source, new Uint8Array(result, destIndex, uncompressedSize - destIndex), sourceIndex, sourceIndex + blockSize);
sourceIndex += blockSize;
}
}
return new Uint8Array(result, 0, uncompressedSize);
}
+1 -2
View File
@@ -3,8 +3,7 @@
"private": true, "private": true,
"scripts": { "scripts": {
"test:cli": "npm run test -w crates/solidity/src/tests/cli-tests", "test:cli": "npm run test -w crates/solidity/src/tests/cli-tests",
"test:wasm": "npm run test:all -w js", "test:wasm": "npm run test:all -w js"
"build:package": "npm run build:package -w js"
}, },
"workspaces": [ "workspaces": [
"crates/solidity/src/tests/cli-tests", "crates/solidity/src/tests/cli-tests",