Compare commits

..

6 Commits

Author SHA1 Message Date
PG Herveou e07d0f0cb7 Move @parity/resolc from js-revive (#296)
- Move npm package from paritytech/js-revive 
- Rename package to `@parity/resolc`
2025-04-30 17:24:52 +02:00
xermicus f6a412eef4 release resolc v0.1.0-dev.14 (#289)
Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
2025-04-24 11:57:46 +02:00
xermicus 20e77cb0b5 configurable stack and heap memory size (#288)
- Allow configuration of the maximum heap and stack size via CLI flags
and JSON input settings.
- Increase the default value for the stack size to 32kb.

---------

Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
2025-04-24 10:47:38 +02:00
xermicus 357bf58868 allow dynamic configuration of the heap memory (#287)
This PR changes the implementation of the emulated EVM heap memory:
Instead of linking in a C implementation the code is emitted directly
into the contract module. Which allows making it configurable via a
compiler parameter (a follow up PR to this).

---------

Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
2025-04-23 20:25:55 +02:00
xermicus f937188991 revive-runner: install with locked dependencies (#286)
Make the installation of  `revive-runner` easier:
- Use locked dependencies to avoid issues with downstream crates
- Make the llvm-context crate an optional dependency to the runner
- Add it to the default `test` target ensuring that this actually works

Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
2025-04-22 19:46:18 +02:00
xermicus 6e44488b4f revive-runner: add a utility binary for local contract execution (#284)
I had this in mind for a while but never implemented a standalone binary
so far because I always end up writing an integration test anyways.

However, using a standalone version of the pallet based on the
revive-runner crate is something people filing in bug reports do
anyways, for example:
https://github.com/paritytech/revive/issues/266
https://github.com/paritytech/contract-issues/issues/54
---------

Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
2025-04-22 15:34:51 +02:00
86 changed files with 1610 additions and 613 deletions
+2 -2
View File
@@ -10,8 +10,8 @@ rustflags = [
"-Clink-arg=-sWASM_ASYNC_COMPILATION=0",
"-Clink-arg=-sDYNAMIC_EXECUTION=0",
"-Clink-arg=-sALLOW_TABLE_GROWTH=1",
"-Clink-arg=--js-library=js/embed/soljson_interface.js",
"-Clink-arg=--pre-js=js/embed/pre.js",
"-Clink-arg=--js-library=js/emscripten/embed/soljson_interface.js",
"-Clink-arg=--pre-js=js/emscripten/embed/pre.js",
"-Clink-arg=-sSTACK_SIZE=128kb",
"-Clink-arg=-sNODEJS_CATCH_EXIT=0"
]
+27
View File
@@ -286,3 +286,30 @@ jobs:
resolc.wasm
resolc_web.js
checksums.txt
npm-release:
needs: [create-release]
runs-on: macos-14
environment: tags
steps:
- name: Build
run: npm -w js/resolc run build
- name: Set version
run: npm -w js/resolc version --no-git-tag-version ${{github.event.release.tag_name}}
- name: npm pack
run: npm -w js/resolc pack
- uses: actions/upload-artifact@v4
with:
name: package
path: "parity-resolc-*.tgz"
- uses: octokit/request-action@bbedc70b1981e610d89f1f8de88311a1fc02fb83
with:
route: POST /repos/paritytech/npm_publish_automation/actions/workflows/publish.yml/dispatches
ref: main
inputs: '${{ format(''{{ "repo": "{0}", "run_id": "{1}" }}'', github.repository, github.run_id) }}'
env:
GITHUB_TOKEN: ${{ secrets.NPM_PUBLISH_AUTOMATION_TOKEN }}
+12 -7
View File
@@ -86,13 +86,18 @@ jobs:
- name: Install Node Packages
run: npm install
- name: Run Playwright tests
run: |
cd js
npx playwright install --with-deps
npx playwright test
- name: Test revive
- name: Test emscripten
run: |
echo "Running tests for ${{ matrix.os }}"
npm run test:wasm
- name: Test @parity/resolc
run: |
echo "Running tests for ${{ matrix.os }}"
npm run -w js/resolc test
- name: Run Playwright tests
run: |
cd js/emscripten
npx playwright install --with-deps
npx playwright test
+1
View File
@@ -1,4 +1,5 @@
/target
/js/resolc/dist
target-llvm
*.dot
.vscode/
+6
View File
@@ -0,0 +1,6 @@
{
"trailingComma": "es5",
"tabWidth": 2,
"semi": false,
"singleQuote": false
}
+15
View File
@@ -10,6 +10,21 @@ Supported `polkadot-sdk` rev:`c29e72a8628835e34deb6aa7db9a78a2e4eabcee`
### Changed
### Fixed
## v0.1.0-dev.14
This is a development pre-release.
Supported `polkadot-sdk` rev:`c29e72a8628835e34deb6aa7db9a78a2e4eabcee`
### Added
- The `revive-runner` helper utility binary which helps to run contracts locally without a blockchain node.
- Allow configuration of the EVM heap memory size and stack size via CLI flags and JSON input settings.
### Changed
- The default PVM stack memory size was increased from 16kb to 32kb.
### Fixed
- Constructors avoid storing zero sized immutable data on exit.
Generated
+18 -15
View File
@@ -4553,7 +4553,7 @@ checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104"
[[package]]
name = "lld-sys"
version = "0.1.0-dev.13"
version = "0.1.0-dev.14"
dependencies = [
"cc",
"libc",
@@ -8266,7 +8266,7 @@ dependencies = [
[[package]]
name = "revive-benchmarks"
version = "0.1.0-dev.13"
version = "0.1.0-dev.14"
dependencies = [
"alloy-primitives",
"criterion",
@@ -8278,18 +8278,18 @@ dependencies = [
[[package]]
name = "revive-build-utils"
version = "0.1.0-dev.13"
version = "0.1.0-dev.14"
[[package]]
name = "revive-builtins"
version = "0.1.0-dev.13"
version = "0.1.0-dev.14"
dependencies = [
"revive-build-utils",
]
[[package]]
name = "revive-common"
version = "0.1.0-dev.13"
version = "0.1.0-dev.14"
dependencies = [
"anyhow",
"serde",
@@ -8299,7 +8299,7 @@ dependencies = [
[[package]]
name = "revive-differential"
version = "0.1.0-dev.13"
version = "0.1.0-dev.14"
dependencies = [
"alloy-genesis",
"alloy-primitives",
@@ -8312,7 +8312,7 @@ dependencies = [
[[package]]
name = "revive-integration"
version = "0.1.0-dev.13"
version = "0.1.0-dev.14"
dependencies = [
"alloy-primitives",
"alloy-sol-types",
@@ -8328,7 +8328,7 @@ dependencies = [
[[package]]
name = "revive-linker"
version = "0.1.0-dev.13"
version = "0.1.0-dev.14"
dependencies = [
"anyhow",
"libc",
@@ -8340,7 +8340,7 @@ dependencies = [
[[package]]
name = "revive-llvm-builder"
version = "0.1.0-dev.13"
version = "0.1.0-dev.14"
dependencies = [
"anyhow",
"assert_cmd",
@@ -8362,7 +8362,7 @@ dependencies = [
[[package]]
name = "revive-llvm-context"
version = "0.1.0-dev.13"
version = "0.1.0-dev.14"
dependencies = [
"anyhow",
"hex",
@@ -8384,9 +8384,12 @@ dependencies = [
[[package]]
name = "revive-runner"
version = "0.1.0-dev.13"
version = "0.1.0-dev.14"
dependencies = [
"alloy-primitives",
"anyhow",
"clap",
"env_logger 0.11.6",
"hex",
"parity-scale-codec",
"polkadot-sdk 0.1.0",
@@ -8400,7 +8403,7 @@ dependencies = [
[[package]]
name = "revive-runtime-api"
version = "0.1.0-dev.13"
version = "0.1.0-dev.14"
dependencies = [
"anyhow",
"inkwell",
@@ -8410,7 +8413,7 @@ dependencies = [
[[package]]
name = "revive-solc-json-interface"
version = "0.1.0-dev.13"
version = "0.1.0-dev.14"
dependencies = [
"anyhow",
"rayon",
@@ -8422,7 +8425,7 @@ dependencies = [
[[package]]
name = "revive-solidity"
version = "0.1.0-dev.13"
version = "0.1.0-dev.14"
dependencies = [
"anyhow",
"clap",
@@ -8449,7 +8452,7 @@ dependencies = [
[[package]]
name = "revive-stdlib"
version = "0.1.0-dev.13"
version = "0.1.0-dev.14"
dependencies = [
"inkwell",
"revive-build-utils",
+15 -15
View File
@@ -3,7 +3,7 @@ resolver = "2"
members = ["crates/*"]
[workspace.package]
version = "0.1.0-dev.13"
version = "0.1.0-dev.14"
authors = [
"Cyrill Leutwiler <cyrill@parity.io>",
"Parity Technologies <admin@parity.io>",
@@ -14,20 +14,20 @@ repository = "https://github.com/paritytech/revive"
rust-version = "1.81.0"
[workspace.dependencies]
revive-benchmarks = { version = "0.1.0-dev.13", path = "crates/benchmarks" }
revive-builtins = { version = "0.1.0-dev.13", path = "crates/builtins" }
revive-common = { version = "0.1.0-dev.13", path = "crates/common" }
revive-differential = { version = "0.1.0-dev.13", path = "crates/differential" }
revive-integration = { version = "0.1.0-dev.13", path = "crates/integration" }
revive-linker = { version = "0.1.0-dev.13", path = "crates/linker" }
lld-sys = { version = "0.1.0-dev.13", path = "crates/lld-sys" }
revive-llvm-context = { version = "0.1.0-dev.13", path = "crates/llvm-context" }
revive-runtime-api = { version = "0.1.0-dev.13", path = "crates/runtime-api" }
revive-runner = { version = "0.1.0-dev.13", path = "crates/runner" }
revive-solc-json-interface = { version = "0.1.0-dev.13", path = "crates/solc-json-interface" }
revive-solidity = { version = "0.1.0-dev.13", path = "crates/solidity" }
revive-stdlib = { version = "0.1.0-dev.13", path = "crates/stdlib" }
revive-build-utils = { version = "0.1.0-dev.13", path = "crates/build-utils" }
revive-benchmarks = { version = "0.1.0-dev.14", path = "crates/benchmarks" }
revive-builtins = { version = "0.1.0-dev.14", path = "crates/builtins" }
revive-common = { version = "0.1.0-dev.14", path = "crates/common" }
revive-differential = { version = "0.1.0-dev.14", path = "crates/differential" }
revive-integration = { version = "0.1.0-dev.14", path = "crates/integration" }
revive-linker = { version = "0.1.0-dev.14", path = "crates/linker" }
lld-sys = { version = "0.1.0-dev.14", path = "crates/lld-sys" }
revive-llvm-context = { version = "0.1.0-dev.14", path = "crates/llvm-context" }
revive-runtime-api = { version = "0.1.0-dev.14", path = "crates/runtime-api" }
revive-runner = { version = "0.1.0-dev.14", path = "crates/runner" }
revive-solc-json-interface = { version = "0.1.0-dev.14", path = "crates/solc-json-interface" }
revive-solidity = { version = "0.1.0-dev.14", path = "crates/solidity" }
revive-stdlib = { version = "0.1.0-dev.14", path = "crates/stdlib" }
revive-build-utils = { version = "0.1.0-dev.14", path = "crates/build-utils" }
hex = "0.4.3"
cc = "1.2"
+6 -5
View File
@@ -5,6 +5,7 @@
install-wasm \
install-llvm-builder \
install-llvm \
install-revive-runner \
format \
clippy \
machete \
@@ -39,6 +40,9 @@ install-llvm: install-llvm-builder
revive-llvm clone
revive-llvm build --llvm-projects lld --llvm-projects clang
install-revive-runner:
cargo install --path crates/runner --no-default-features --locked
format:
cargo fmt --all --check
@@ -49,7 +53,7 @@ machete:
cargo install cargo-machete
cargo machete
test: format clippy machete test-cli test-workspace
test: format clippy machete test-cli test-workspace install-revive-runner
test-integration: install-bin
cargo test --package revive-integration
@@ -88,7 +92,4 @@ clean:
rm -rf node_modules ; \
rm -rf crates/solidity/src/tests/cli-tests/artifacts ; \
cargo uninstall revive-solidity ; \
cargo uninstall revive-llvm-builder ; \
rm -f package-lock.json ; \
rm -rf js/dist ; \
rm -f js/src/resolc.{wasm,js}
cargo uninstall revive-llvm-builder ;
+8 -8
View File
@@ -1,10 +1,10 @@
{
"Baseline": 950,
"Computation": 2222,
"DivisionArithmetics": 8802,
"ERC20": 17601,
"Events": 1628,
"FibonacciIterative": 1485,
"Flipper": 2089,
"SHA1": 8230
"Baseline": 939,
"Computation": 2282,
"DivisionArithmetics": 8849,
"ERC20": 18308,
"Events": 1640,
"FibonacciIterative": 1497,
"Flipper": 2099,
"SHA1": 8243
}
+3 -3
View File
@@ -5,6 +5,7 @@ use std::sync::OnceLock;
pub use self::debug_config::ir_type::IRType as DebugConfigIR;
pub use self::debug_config::DebugConfig;
pub use self::memory::MemoryConfig;
pub use self::optimizer::settings::size_level::SizeLevel as OptimizerSettingsSizeLevel;
pub use self::optimizer::settings::Settings as OptimizerSettings;
pub use self::optimizer::Optimizer;
@@ -28,6 +29,7 @@ pub use self::polkavm::context::function::runtime::entry::Entry as PolkaVMEntryF
pub use self::polkavm::context::function::runtime::revive::Exit as PolkaVMExitFunction;
pub use self::polkavm::context::function::runtime::revive::WordToPointer as PolkaVMWordToPointerFunction;
pub use self::polkavm::context::function::runtime::runtime_code::RuntimeCode as PolkaVMRuntimeCodeFunction;
pub use self::polkavm::context::function::runtime::sbrk::Sbrk as PolkaVMSbrkFunction;
pub use self::polkavm::context::function::runtime::FUNCTION_DEPLOY_CODE as PolkaVMFunctionDeployCode;
pub use self::polkavm::context::function::runtime::FUNCTION_ENTRY as PolkaVMFunctionEntry;
pub use self::polkavm::context::function::runtime::FUNCTION_RUNTIME_CODE as PolkaVMFunctionRuntimeCode;
@@ -48,8 +50,6 @@ pub use self::polkavm::context::Context as PolkaVMContext;
pub use self::polkavm::evm::arithmetic as polkavm_evm_arithmetic;
pub use self::polkavm::evm::bitwise as polkavm_evm_bitwise;
pub use self::polkavm::evm::call as polkavm_evm_call;
pub use self::polkavm::evm::call::Call as PolkaVMCallFunction;
pub use self::polkavm::evm::call::CallReentrancyHeuristic as PolkaVMCallReentrancyHeuristicFunction;
pub use self::polkavm::evm::calldata as polkavm_evm_calldata;
pub use self::polkavm::evm::comparison as polkavm_evm_comparison;
pub use self::polkavm::evm::context as polkavm_evm_contract_context;
@@ -62,7 +62,6 @@ pub use self::polkavm::evm::ext_code as polkavm_evm_ext_code;
pub use self::polkavm::evm::immutable as polkavm_evm_immutable;
pub use self::polkavm::evm::immutable::Load as PolkaVMLoadImmutableDataFunction;
pub use self::polkavm::evm::immutable::Store as PolkaVMStoreImmutableDataFunction;
pub use self::polkavm::evm::math as polkavm_evm_math;
pub use self::polkavm::evm::memory as polkavm_evm_memory;
pub use self::polkavm::evm::r#return as polkavm_evm_return;
@@ -77,6 +76,7 @@ pub use self::target_machine::target::Target;
pub use self::target_machine::TargetMachine;
pub(crate) mod debug_config;
pub(crate) mod memory;
pub(crate) mod optimizer;
pub(crate) mod polkavm;
pub(crate) mod target_machine;
+21
View File
@@ -0,0 +1,21 @@
//! The compile time PolkaVM memory configuration settings.
use serde::{Deserialize, Serialize};
/// The PolkaVM memory configuration.
#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
pub struct MemoryConfig {
/// The emulated EVM linear heap memory size in bytes.
pub heap_size: u32,
/// The PVM stack size in bytes.
pub stack_size: u32,
}
impl Default for MemoryConfig {
fn default() -> Self {
Self {
heap_size: 64 * 1024,
stack_size: 32 * 1024,
}
}
}
@@ -9,6 +9,12 @@ pub static XLEN: usize = revive_common::BIT_LENGTH_X32;
/// The calldata size global variable name.
pub static GLOBAL_CALLDATA_SIZE: &str = "calldatasize";
/// The heap size global variable name.
pub static GLOBAL_HEAP_SIZE: &str = "__heap_size";
/// The heap memory global variable name.
pub static GLOBAL_HEAP_MEMORY: &str = "__heap_memory";
/// The spill buffer global variable name.
pub static GLOBAL_ADDRESS_SPILL_BUFFER: &str = "address_spill_buffer";
@@ -66,7 +66,7 @@ impl<'ctx> Argument<'ctx> {
/// Access the underlying value.
///
/// Will emit a stack load if `self` is a pointer argument.
pub fn to_value<D: crate::polkavm::Dependency + Clone>(
pub fn access<D: crate::polkavm::Dependency + Clone>(
&self,
context: &crate::polkavm::context::Context<'ctx, D>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>> {
@@ -78,8 +78,8 @@ impl<'ctx> Argument<'ctx> {
/// Access the underlying value.
///
/// Will emit a stack store if `self` is a value argument.
pub fn to_pointer<D: crate::polkavm::Dependency + Clone>(
/// Will emit a stack load if `self` is a pointer argument.
pub fn as_pointer<D: crate::polkavm::Dependency + Clone>(
&self,
context: &crate::polkavm::context::Context<'ctx, D>,
) -> anyhow::Result<crate::polkavm::context::Pointer<'ctx>> {
@@ -31,6 +31,23 @@ impl Entry {
context.xlen_type().get_undef(),
);
context.set_global(
crate::polkavm::GLOBAL_HEAP_SIZE,
context.xlen_type(),
AddressSpace::Stack,
context.xlen_type().const_zero(),
);
let heap_memory_type = context
.byte_type()
.array_type(context.memory_config.heap_size);
context.set_global(
crate::polkavm::GLOBAL_HEAP_MEMORY,
heap_memory_type,
AddressSpace::Stack,
heap_memory_type.const_zero(),
);
let address_type = context.integer_type(revive_common::BIT_LENGTH_ETH_ADDRESS);
context.set_global(
crate::polkavm::GLOBAL_ADDRESS_SPILL_BUFFER,
@@ -5,6 +5,7 @@ pub mod deploy_code;
pub mod entry;
pub mod revive;
pub mod runtime_code;
pub mod sbrk;
/// The main entry function name.
pub const FUNCTION_ENTRY: &str = "__entry";
@@ -0,0 +1,144 @@
//! Emulates the linear EVM heap memory via a simulated `sbrk` system call.
use inkwell::values::BasicValue;
use crate::polkavm::context::attribute::Attribute;
use crate::polkavm::context::runtime::RuntimeFunction;
use crate::polkavm::context::Context;
use crate::polkavm::Dependency;
use crate::polkavm::WriteLLVM;
/// Simulates the `sbrk` system call, reproducing the semantics of the EVM heap memory.
///
/// Parameters:
/// - The `offset` into the emulated EVM heap memory.
/// - The `size` of the allocation emulated EVM heap memory.
///
/// Returns:
/// - A pointer to the EVM heap memory at given `offset`.
///
/// Semantics:
/// - Traps if the offset is out of bounds.
/// - Aligns the total heap memory size to the EVM word size.
/// - Traps if the memory size would be greater than the configured EVM heap memory size.
/// - Maintains the total memory size (`msize`) in global heap size value.
pub struct Sbrk;
impl<D> RuntimeFunction<D> for Sbrk
where
D: Dependency + Clone,
{
const NAME: &'static str = "__sbrk_internal";
const ATTRIBUTES: &'static [Attribute] = &[
Attribute::NoFree,
Attribute::NoRecurse,
Attribute::WillReturn,
];
fn r#type<'ctx>(context: &Context<'ctx, D>) -> inkwell::types::FunctionType<'ctx> {
context.llvm().ptr_type(Default::default()).fn_type(
&[context.xlen_type().into(), context.xlen_type().into()],
false,
)
}
fn emit_body<'ctx>(
&self,
context: &mut Context<'ctx, D>,
) -> anyhow::Result<Option<inkwell::values::BasicValueEnum<'ctx>>> {
let offset = Self::paramater(context, 0).into_int_value();
let size = Self::paramater(context, 1).into_int_value();
let trap_block = context.append_basic_block("trap");
let offset_in_bounds_block = context.append_basic_block("offset_in_bounds");
let is_offset_out_of_bounds = context.builder().build_int_compare(
inkwell::IntPredicate::UGE,
offset,
context.heap_size(),
"offset_out_of_bounds",
)?;
context.build_conditional_branch(
is_offset_out_of_bounds,
trap_block,
offset_in_bounds_block,
)?;
context.set_basic_block(trap_block);
context.build_call(context.intrinsics().trap, &[], "invalid_trap");
context.build_unreachable();
context.set_basic_block(offset_in_bounds_block);
let mask = context
.xlen_type()
.const_int(revive_common::BYTE_LENGTH_WORD as u64 - 1, false);
let total_size = context
.builder()
.build_int_add(offset, size, "total_size")?;
let memory_size = context.builder().build_and(
context.builder().build_int_add(total_size, mask, "mask")?,
context.builder().build_not(mask, "mask_not")?,
"memory_size",
)?;
let size_in_bounds_block = context.append_basic_block("size_in_bounds");
let is_size_out_of_bounds = context.builder().build_int_compare(
inkwell::IntPredicate::UGT,
memory_size,
context.heap_size(),
"size_out_of_bounds",
)?;
context.build_conditional_branch(
is_size_out_of_bounds,
trap_block,
size_in_bounds_block,
)?;
context.set_basic_block(size_in_bounds_block);
let return_block = context.append_basic_block("return_pointer");
let new_size_block = context.append_basic_block("new_size");
let is_new_size = context.builder().build_int_compare(
inkwell::IntPredicate::UGT,
memory_size,
context
.get_global_value(crate::polkavm::GLOBAL_HEAP_SIZE)?
.into_int_value(),
"is_new_size",
)?;
context.build_conditional_branch(is_new_size, new_size_block, return_block)?;
context.set_basic_block(new_size_block);
context.build_store(
context.get_global(crate::polkavm::GLOBAL_HEAP_SIZE)?.into(),
memory_size,
)?;
context.build_unconditional_branch(return_block);
context.set_basic_block(return_block);
Ok(Some(
context
.build_gep(
context
.get_global(crate::polkavm::GLOBAL_HEAP_MEMORY)?
.into(),
&[context.xlen_type().const_zero(), offset],
context.byte_type(),
"allocation_start_pointer",
)
.value
.as_basic_value_enum(),
))
}
}
impl<D> WriteLLVM<D> for Sbrk
where
D: Dependency + Clone,
{
fn declare(&mut self, context: &mut Context<D>) -> anyhow::Result<()> {
<Self as RuntimeFunction<_>>::declare(self, context)
}
fn into_llvm(self, context: &mut Context<D>) -> anyhow::Result<()> {
<Self as RuntimeFunction<_>>::emit(&self, context)
}
}
+41 -23
View File
@@ -26,6 +26,7 @@ use inkwell::debug_info::DIScope;
use inkwell::types::BasicType;
use inkwell::values::BasicValue;
use crate::memory::MemoryConfig;
use crate::optimizer::settings::Settings as OptimizerSettings;
use crate::optimizer::Optimizer;
use crate::polkavm::DebugConfig;
@@ -33,6 +34,7 @@ use crate::polkavm::Dependency;
use crate::target_machine::target::Target;
use crate::target_machine::TargetMachine;
use crate::PolkaVMLoadHeapWordFunction;
use crate::PolkaVMSbrkFunction;
use crate::PolkaVMStoreHeapWordFunction;
use self::address_space::AddressSpace;
@@ -85,6 +87,8 @@ where
loop_stack: Vec<Loop<'ctx>>,
/// The extra LLVM arguments that were used during target initialization.
llvm_arguments: &'ctx [String],
/// The PVM memory configuration.
memory_config: MemoryConfig,
/// The project dependency manager. It can be any entity implementing the trait.
/// The manager is used to get information about contracts and their dependencies during
@@ -116,9 +120,6 @@ where
/// The loop stack default capacity.
const LOOP_STACK_INITIAL_CAPACITY: usize = 16;
/// The PolkaVM minimum stack size.
const POLKAVM_STACK_SIZE: u32 = 0x4000;
/// Link in the stdlib module.
fn link_stdlib_module(
llvm: &'ctx inkwell::context::Context,
@@ -218,6 +219,7 @@ where
}
/// Initializes a new LLVM context.
#[allow(clippy::too_many_arguments)]
pub fn new(
llvm: &'ctx inkwell::context::Context,
module: inkwell::module::Module<'ctx>,
@@ -226,11 +228,12 @@ where
include_metadata_hash: bool,
debug_config: DebugConfig,
llvm_arguments: &'ctx [String],
memory_config: MemoryConfig,
) -> Self {
Self::set_data_layout(llvm, &module);
Self::link_stdlib_module(llvm, &module);
Self::link_polkavm_imports(llvm, &module);
Self::set_polkavm_stack_size(llvm, &module, Self::POLKAVM_STACK_SIZE);
Self::set_polkavm_stack_size(llvm, &module, memory_config.stack_size);
Self::set_module_flags(llvm, &module);
let intrinsics = Intrinsics::new(llvm, &module);
@@ -254,6 +257,7 @@ where
current_function: None,
loop_stack: Vec::with_capacity(Self::LOOP_STACK_INITIAL_CAPACITY),
llvm_arguments,
memory_config,
dependency_manager,
include_metadata_hash,
@@ -644,6 +648,7 @@ where
self.include_metadata_hash,
self.debug_config.clone(),
self.llvm_arguments,
self.memory_config,
)
})
}
@@ -1084,32 +1089,40 @@ where
offset: inkwell::values::IntValue<'ctx>,
size: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::PointerValue<'ctx>> {
Ok(self
.builder()
.build_call(
self.runtime_api_method(revive_runtime_api::polkavm_imports::SBRK),
&[offset.into(), size.into()],
"call_sbrk",
)?
let call_site_value = self.builder().build_call(
<PolkaVMSbrkFunction as RuntimeFunction<D>>::declaration(self).function_value(),
&[offset.into(), size.into()],
"alloc_start",
)?;
call_site_value.add_attribute(
inkwell::attributes::AttributeLoc::Return,
self.llvm
.create_enum_attribute(Attribute::NonNull as u32, 0),
);
call_site_value.add_attribute(
inkwell::attributes::AttributeLoc::Return,
self.llvm
.create_enum_attribute(Attribute::NoUndef as u32, 0),
);
Ok(call_site_value
.try_as_basic_value()
.left()
.expect("sbrk returns a pointer")
.unwrap_or_else(|| {
panic!(
"revive runtime function {} should return a value",
<PolkaVMSbrkFunction as RuntimeFunction<D>>::NAME,
)
})
.into_pointer_value())
}
/// Build a call to PolkaVM `msize` for querying the linear memory size.
pub fn build_msize(&self) -> anyhow::Result<inkwell::values::IntValue<'ctx>> {
let memory_size_pointer = self
.module()
.get_global(revive_runtime_api::polkavm_imports::MEMORY_SIZE)
.expect("the memory size symbol should have been declared")
.as_pointer_value();
let memory_size_value = self.builder().build_load(
self.xlen_type(),
memory_size_pointer,
"memory_size_value",
)?;
Ok(memory_size_value.into_int_value())
Ok(self
.get_global_value(crate::polkavm::GLOBAL_HEAP_SIZE)?
.into_int_value())
}
/// Returns a pointer to `offset` into the heap, allocating
@@ -1428,4 +1441,9 @@ where
pub fn optimizer_settings(&self) -> &OptimizerSettings {
self.optimizer.settings()
}
pub fn heap_size(&self) -> inkwell::values::IntValue<'ctx> {
self.xlen_type()
.const_int(self.memory_config.heap_size as u64, false)
}
}
@@ -23,6 +23,7 @@ pub fn create_context(
true,
Default::default(),
Default::default(),
Default::default(),
)
}
+81 -378
View File
@@ -2,310 +2,117 @@
use inkwell::values::BasicValue;
use crate::polkavm::context::address_space::AddressSpace;
use crate::polkavm::context::argument::Argument;
use crate::polkavm::context::pointer::Pointer;
use crate::polkavm::context::runtime::RuntimeFunction;
use crate::polkavm::context::Context;
use crate::polkavm::Dependency;
use crate::polkavm::WriteLLVM;
const STATIC_CALL_FLAG: u32 = 0b0001_0000;
const REENTRANT_CALL_FLAG: u32 = 0b0000_1000;
const SOLIDITY_TRANSFER_GAS_STIPEND_THRESHOLD: u64 = 2300;
pub struct Call;
impl<D> RuntimeFunction<D> for Call
where
D: Dependency + Clone,
{
const NAME: &'static str = "__revive_call";
fn r#type<'ctx>(context: &Context<'ctx, D>) -> inkwell::types::FunctionType<'ctx> {
context.xlen_type().fn_type(
&[
context.xlen_type().into(),
context.xlen_type().into(),
context.xlen_type().into(),
context.xlen_type().into(),
context.xlen_type().into(),
context.llvm().ptr_type(AddressSpace::Stack.into()).into(),
context.llvm().ptr_type(AddressSpace::Stack.into()).into(),
context.llvm().ptr_type(AddressSpace::Stack.into()).into(),
],
false,
)
}
fn emit_body<'ctx>(
&self,
context: &mut Context<'ctx, D>,
) -> anyhow::Result<Option<inkwell::values::BasicValueEnum<'ctx>>> {
let flags = Self::paramater(context, 0).into_int_value();
let input_length = Self::paramater(context, 1).into_int_value();
let output_length = Self::paramater(context, 2).into_int_value();
let input_pointer = Self::paramater(context, 3).into_int_value();
let output_pointer = Self::paramater(context, 4).into_int_value();
let address_pointer = Self::paramater(context, 5).into_pointer_value();
let value_pointer = Self::paramater(context, 6).into_pointer_value();
let deposit_pointer = Self::paramater(context, 7).into_pointer_value();
let output_length_pointer =
context.build_alloca_at_entry(context.xlen_type(), "output_length");
context.build_store(output_length_pointer, output_length)?;
let input_pointer = context.build_heap_gep(input_pointer, input_length)?;
let output_pointer = context.build_heap_gep(output_pointer, output_length)?;
let flags_and_callee = revive_runtime_api::calling_convention::pack_hi_lo_reg(
context.builder(),
context.llvm(),
flags,
Pointer::new(context.word_type(), AddressSpace::Stack, 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(),
Pointer::new(context.word_type(), AddressSpace::Stack, deposit_pointer).to_int(context),
Pointer::new(context.word_type(), AddressSpace::Stack, 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 success = context
.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"))
.into_int_value();
let is_success = context.builder().build_int_compare(
inkwell::IntPredicate::EQ,
success,
context.integer_const(revive_common::BIT_LENGTH_X64, 0),
"is_success",
)?;
Ok(context
.builder()
.build_int_z_extend(is_success, context.xlen_type(), "success")?
.as_basic_value_enum()
.into())
}
}
impl<D> WriteLLVM<D> for Call
where
D: Dependency + Clone,
{
fn declare(&mut self, context: &mut Context<D>) -> anyhow::Result<()> {
<Self as RuntimeFunction<_>>::declare(self, context)
}
fn into_llvm(self, context: &mut Context<D>) -> anyhow::Result<()> {
<Self as RuntimeFunction<_>>::emit(&self, context)
}
}
/// Translates a contract call.
#[allow(clippy::too_many_arguments)]
pub fn call<'ctx, D>(
context: &mut Context<'ctx, D>,
gas: &Argument<'ctx>,
address: &Argument<'ctx>,
value: Option<&Argument<'ctx>>,
input_offset: &Argument<'ctx>,
input_length: &Argument<'ctx>,
output_offset: &Argument<'ctx>,
output_length: &Argument<'ctx>,
gas: inkwell::values::IntValue<'ctx>,
address: inkwell::values::IntValue<'ctx>,
value: Option<inkwell::values::IntValue<'ctx>>,
input_offset: inkwell::values::IntValue<'ctx>,
input_length: inkwell::values::IntValue<'ctx>,
output_offset: inkwell::values::IntValue<'ctx>,
output_length: inkwell::values::IntValue<'ctx>,
_constants: Vec<Option<num::BigUint>>,
static_call: bool,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
let input_offset =
context.safe_truncate_int_to_xlen(input_offset.to_value(context)?.into_int_value())?;
let input_length =
context.safe_truncate_int_to_xlen(input_length.to_value(context)?.into_int_value())?;
let address_pointer = context.build_address_argument_store(address)?;
let output_offset =
context.safe_truncate_int_to_xlen(output_offset.to_value(context)?.into_int_value())?;
let output_length =
context.safe_truncate_int_to_xlen(output_length.to_value(context)?.into_int_value())?;
let value = value.unwrap_or_else(|| context.word_const(0));
let value_pointer = context.build_alloca_at_entry(context.word_type(), "value_pointer");
context.build_store(value_pointer, value)?;
let deposit_limit_pointer =
context.build_alloca_at_entry(context.word_type(), "deposit_limit_pointer");
let input_offset = context.safe_truncate_int_to_xlen(input_offset)?;
let input_length = context.safe_truncate_int_to_xlen(input_length)?;
let output_offset = context.safe_truncate_int_to_xlen(output_offset)?;
let output_length = context.safe_truncate_int_to_xlen(output_length)?;
let flags = if static_call {
context.build_store(deposit_limit_pointer, context.word_type().const_zero())?;
let input_pointer = context.build_heap_gep(input_offset, input_length)?;
let output_pointer = context.build_heap_gep(output_offset, output_length)?;
let output_length_pointer = context.build_alloca_at_entry(context.xlen_type(), "output_length");
context.build_store(output_length_pointer, output_length)?;
let (flags, deposit_limit_value) = if static_call {
let flags = REENTRANT_CALL_FLAG | STATIC_CALL_FLAG;
context.xlen_type().const_int(flags as u64, false)
(
context.xlen_type().const_int(flags as u64, false),
context.word_type().const_zero(),
)
} else {
let name = <CallReentrancyHeuristic as RuntimeFunction<D>>::NAME;
let declaration = <CallReentrancyHeuristic as RuntimeFunction<D>>::declaration(context);
let gas = context.builder().build_int_truncate(
gas.to_value(context)?.into_int_value(),
context.xlen_type(),
"gas",
)?;
let arguments = &[
input_length.into(),
output_length.into(),
gas.into(),
deposit_limit_pointer.value.into(),
];
context
.build_call(declaration, arguments, "flags")
.unwrap_or_else(|| panic!("runtime function {name} should return a value"))
.into_int_value()
call_reentrancy_heuristic(context, gas, input_length, output_length)?
};
let value_pointer = match value {
Some(argument) => argument.to_pointer(context)?,
None => {
let value_pointer = context.build_alloca_at_entry(context.word_type(), "value_pointer");
context.build_store(value_pointer, context.word_const(0))?;
value_pointer
}
};
let deposit_pointer = context.build_alloca_at_entry(context.word_type(), "deposit_pointer");
context.build_store(deposit_pointer, deposit_limit_value)?;
let address_pointer =
context.build_address_argument_store(address.to_value(context)?.into_int_value())?;
let flags_and_callee = revive_runtime_api::calling_convention::pack_hi_lo_reg(
context.builder(),
context.llvm(),
flags,
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 = <Call as RuntimeFunction<D>>::NAME;
let arguments = &[
flags.into(),
input_length.into(),
output_length.into(),
input_offset.into(),
output_offset.into(),
address_pointer.value.into(),
value_pointer.value.into(),
deposit_limit_pointer.value.into(),
];
let declaration = <Call as RuntimeFunction<D>>::declaration(context);
let result = context
.build_call(declaration, arguments, "call_result_truncated")
.unwrap_or_else(|| panic!("runtime function {name} should return a value"))
let name = revive_runtime_api::polkavm_imports::CALL;
let success = context
.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"))
.into_int_value();
let is_success = context.builder().build_int_compare(
inkwell::IntPredicate::EQ,
success,
context.integer_const(revive_common::BIT_LENGTH_X64, 0),
"is_success",
)?;
Ok(context
.builder()
.build_int_z_extend(result, context.word_type(), "call_result")?
.build_int_z_extend(is_success, context.word_type(), "success")?
.as_basic_value_enum())
/*
let address_pointer = context.build_address_argument_store(address)?;
let value = value.unwrap_or_else(|| context.word_const(0));
let value_pointer = context.build_alloca_at_entry(context.word_type(), "value_pointer");
context.build_store(value_pointer, value)?;
let input_offset = context.safe_truncate_int_to_xlen(input_offset)?;
let input_length = context.safe_truncate_int_to_xlen(input_length)?;
let output_offset = context.safe_truncate_int_to_xlen(output_offset)?;
let output_length = context.safe_truncate_int_to_xlen(output_length)?;
let input_pointer = context.build_heap_gep(input_offset, input_length)?;
let output_pointer = context.build_heap_gep(output_offset, output_length)?;
let output_length_pointer = context.build_alloca_at_entry(context.xlen_type(), "output_length");
context.build_store(output_length_pointer, output_length)?;
let (flags, deposit_limit_value) = if static_call {
let flags = REENTRANT_CALL_FLAG | STATIC_CALL_FLAG;
(
context.xlen_type().const_int(flags as u64, false),
context.word_type().const_zero(),
)
} else {
call_reentrancy_heuristic(context, gas, input_length, output_length)?
};
let deposit_pointer = context.build_alloca_at_entry(context.word_type(), "deposit_pointer");
context.build_store(deposit_pointer, deposit_limit_value)?;
let flags_and_callee = revive_runtime_api::calling_convention::pack_hi_lo_reg(
context.builder(),
context.llvm(),
flags,
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 success = context
.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"))
.into_int_value();
let is_success = context.builder().build_int_compare(
inkwell::IntPredicate::EQ,
success,
context.integer_const(revive_common::BIT_LENGTH_X64, 0),
"is_success",
)?;
Ok(context
.builder()
.build_int_z_extend(is_success, context.word_type(), "success")?
.as_basic_value_enum())
*/
}
#[allow(clippy::too_many_arguments)]
@@ -409,110 +216,6 @@ where
.as_basic_value_enum())
}
pub struct CallReentrancyHeuristic;
impl<D> RuntimeFunction<D> for CallReentrancyHeuristic
where
D: Dependency + Clone,
{
const NAME: &'static str = "__revive_call_reentrancy_heuristic";
fn r#type<'ctx>(context: &Context<'ctx, D>) -> inkwell::types::FunctionType<'ctx> {
context.xlen_type().fn_type(
&[
// Input length
context.xlen_type().into(),
// Output length
context.xlen_type().into(),
// Gas
context.xlen_type().into(),
// Deposit limit value pointer
context.llvm().ptr_type(AddressSpace::Stack.into()).into(),
],
false,
)
}
fn emit_body<'ctx>(
&self,
context: &mut Context<'ctx, D>,
) -> anyhow::Result<Option<inkwell::values::BasicValueEnum<'ctx>>> {
let input_length = Self::paramater(context, 0).into_int_value();
let output_length = Self::paramater(context, 1).into_int_value();
let gas = Self::paramater(context, 2).into_int_value();
let deposit_pointer = Self::paramater(context, 3).into_pointer_value();
// Branch-free SSA implementation: First derive the heuristic boolean (int1) value.
let input_length_or_output_length = context.builder().build_or(
input_length,
output_length,
"input_length_or_output_length",
)?;
let is_no_input_no_output = context.builder().build_int_compare(
inkwell::IntPredicate::EQ,
context.xlen_type().const_zero(),
input_length_or_output_length,
"is_no_input_no_output",
)?;
let gas_stipend = context
.xlen_type()
.const_int(SOLIDITY_TRANSFER_GAS_STIPEND_THRESHOLD, false);
let is_gas_stipend_for_transfer_or_send = context.builder().build_int_compare(
inkwell::IntPredicate::EQ,
gas,
gas_stipend,
"is_gas_stipend_for_transfer_or_send",
)?;
let is_balance_transfer = context.builder().build_and(
is_no_input_no_output,
is_gas_stipend_for_transfer_or_send,
"is_balance_transfer",
)?;
let is_regular_call = context
.builder()
.build_not(is_balance_transfer, "is_balance_transfer_inverted")?;
// Call flag: Left shift the heuristic boolean value.
let is_regular_call_xlen = context.builder().build_int_z_extend(
is_regular_call,
context.xlen_type(),
"is_balance_transfer_xlen",
)?;
let call_flags = context.builder().build_left_shift(
is_regular_call_xlen,
context.xlen_type().const_int(3, false),
"flags",
)?;
// Deposit limit value: Sign-extended the heuristic boolean value.
let deposit_limit_value = context.builder().build_int_s_extend(
is_regular_call,
context.word_type(),
"deposit_limit_value",
)?;
context.build_store(
Pointer::new(context.word_type(), AddressSpace::Stack, deposit_pointer),
deposit_limit_value,
)?;
Ok(Some(call_flags.into()))
}
}
impl<D> WriteLLVM<D> for CallReentrancyHeuristic
where
D: Dependency + Clone,
{
fn declare(&mut self, context: &mut Context<D>) -> anyhow::Result<()> {
<Self as RuntimeFunction<_>>::declare(self, context)
}
fn into_llvm(self, context: &mut Context<D>) -> anyhow::Result<()> {
<Self as RuntimeFunction<_>>::emit(&self, context)
}
}
/// The Solidity `address.transfer` and `address.send` call detection heuristic.
///
/// # Why
@@ -533,7 +236,7 @@ where
///
/// # Returns
/// The call flags xlen `IntValue` and the deposit limit word `IntValue`.
fn _call_reentrancy_heuristic<'ctx, D>(
fn call_reentrancy_heuristic<'ctx, D>(
context: &mut Context<'ctx, D>,
gas: inkwell::values::IntValue<'ctx>,
input_length: inkwell::values::IntValue<'ctx>,
@@ -19,7 +19,7 @@ where
{
let name = <PolkaVMLoadStorageWordFunction as RuntimeFunction<D>>::NAME;
let declaration = <PolkaVMLoadStorageWordFunction as RuntimeFunction<D>>::declaration(context);
let arguments = [position.to_pointer(context)?.value.into()];
let arguments = [position.as_pointer(context)?.value.into()];
Ok(context
.build_call(declaration, &arguments, "storage_load")
.unwrap_or_else(|| panic!("runtime function {name} should return a value")))
@@ -36,8 +36,8 @@ where
{
let declaration = <PolkaVMStoreStorageWordFunction as RuntimeFunction<D>>::declaration(context);
let arguments = [
position.to_pointer(context)?.value.into(),
value.to_pointer(context)?.value.into(),
position.as_pointer(context)?.value.into(),
value.as_pointer(context)?.value.into(),
];
context.build_call(declaration, &arguments, "storage_store");
Ok(())
@@ -52,7 +52,7 @@ where
D: Dependency + Clone,
{
let name = <PolkaVMLoadTransientStorageWordFunction as RuntimeFunction<D>>::NAME;
let arguments = [position.to_pointer(context)?.value.into()];
let arguments = [position.as_pointer(context)?.value.into()];
let declaration =
<PolkaVMLoadTransientStorageWordFunction as RuntimeFunction<D>>::declaration(context);
Ok(context
@@ -72,8 +72,8 @@ where
let declaration =
<PolkaVMStoreTransientStorageWordFunction as RuntimeFunction<D>>::declaration(context);
let arguments = [
position.to_pointer(context)?.value.into(),
value.to_pointer(context)?.value.into(),
position.as_pointer(context)?.value.into(),
value.as_pointer(context)?.value.into(),
];
context.build_call(declaration, &arguments, "transient_storage_store");
Ok(())
+3
View File
@@ -7,6 +7,7 @@ pub mod evm;
pub use self::r#const::*;
use crate::debug_config::DebugConfig;
use crate::memory::MemoryConfig;
use crate::optimizer::settings::Settings as OptimizerSettings;
use anyhow::Context as AnyhowContext;
@@ -90,6 +91,7 @@ pub trait Dependency {
include_metadata_hash: bool,
debug_config: DebugConfig,
llvm_arguments: &[String],
memory_config: MemoryConfig,
) -> anyhow::Result<String>;
/// Resolves a full contract path.
@@ -111,6 +113,7 @@ impl Dependency for DummyDependency {
_include_metadata_hash: bool,
_debug_config: DebugConfig,
_llvm_arguments: &[String],
_memory_config: MemoryConfig,
) -> anyhow::Result<String> {
Ok(String::new())
}
+9 -2
View File
@@ -10,12 +10,19 @@ description = "Execute revive contracts in a simulated blockchain runtime"
[package.metadata.cargo-machete]
ignored = ["codec", "scale-info"]
[[bin]]
name = "revive-runner"
path = "src/main.rs"
[features]
std = ["polkadot-sdk/std"]
default = ["solidity"]
solidity = ["revive-solidity", "revive-differential"]
solidity = ["revive-solidity", "revive-differential", "revive-llvm-context"]
[dependencies]
env_logger = { workspace = true }
clap = { workspace = true, features = ["help", "std", "derive"] }
anyhow = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
hex = { workspace = true, features = ["serde"] }
@@ -34,4 +41,4 @@ polkadot-sdk.features = [
revive-solidity = { workspace = true, optional = true }
revive-differential = { workspace = true, optional = true }
revive-llvm-context = { workspace = true }
revive-llvm-context = { workspace = true, optional = true }
+26
View File
@@ -0,0 +1,26 @@
# revive-runner
The revive runner is a helper utility aiding in contract debugging.
Given a PVM contract blob, it will upload, deploy and call that contract using a local, stand-alone un-blockchained pallet revive (which is our execution layer).
This is somewhat similar to the geth `evm` utility binary.
## Installation
The `revive-runner` does not depend on the compiler itself, hence installing this utility does not depend on LLVM, so no LLVM build is required.
Inside the root `revive` repository directory, execute:
```bash
make install-revive-runner
```
Which will install the `revive-runner` using `cargo`.
## Usage
Set the `RUST_LOG` environment varibale to the `trace` level to see the full PolkaVM execution trace. For example:
```bash
RUST_LOG=trace revive-runner -f mycontract.pvm -c a9059cbb000000000000000000000000f24ff3a9cf04c71dbc94d0b566f7a27b94566cac0000000000000000000000000000000000000000000000000000000000000000
+93
View File
@@ -0,0 +1,93 @@
use std::path::PathBuf;
use clap::Parser;
use revive_runner::{Code, OptionalHex, Specs, SpecsAction::*, TestAddress};
/// Execute revive PolkaVM contracts locally.
#[derive(Parser)]
#[command(version, about, long_about = None)]
struct Arguments {
/// The hex encoded calldata for the contract call.
#[arg(short, long)]
calldata: Option<String>,
/// The hex encoded calldata for the contract deployment.
#[arg(short, long)]
deploy_calldata: Option<String>,
/// The hex encoded contract code blob to instantiate and execute.
#[arg(short, long)]
blob: Option<String>,
/// The contract code to instantiate and execute.
#[arg(short, long)]
file: Option<PathBuf>,
/// The origin account used to initiate the deploy and call transactions.
#[arg(short, long)]
origin: Option<TestAddress>,
/// The value the call transaction is endowed with.
#[arg(short, long)]
value: Option<u128>,
/// The value the deploy transaction is endowed with.
#[arg(long)]
deploy_value: Option<u128>,
}
fn main() -> anyhow::Result<()> {
env_logger::init();
let arguments = Arguments::parse();
let code = match (arguments.blob, arguments.file) {
(Some(blob), None) => hex::decode(blob)
.map_err(|error| anyhow::anyhow!("expected hex encoded PVM blob: {error}"))?,
(None, Some(file)) => std::fs::read(&file).map_err(|error| {
anyhow::anyhow!("unable to read PVM file {}: {error}", file.display())
})?,
_ => anyhow::bail!("should either provide a PVM blob or a PVM file"),
};
let calldata = match arguments.calldata {
Some(calldata) => hex::decode(calldata)
.map_err(|error| anyhow::anyhow!("expected hex encoded calldata: {error}"))?,
None => vec![],
};
let deploy_calldata = match arguments.deploy_calldata {
Some(calldata) => hex::decode(calldata)
.map_err(|error| anyhow::anyhow!("expected hex encoded calldata: {error}"))?,
None => vec![],
};
let origin = arguments.origin.unwrap_or(TestAddress::Alice);
let actions = vec![
Instantiate {
origin: origin.clone(),
value: arguments.deploy_value.unwrap_or(0),
gas_limit: None,
storage_deposit_limit: None,
code: Code::Bytes(code),
data: deploy_calldata,
salt: OptionalHex::default(),
},
Call {
origin,
dest: TestAddress::Instantiated(0),
value: arguments.value.unwrap_or(0),
gas_limit: None,
storage_deposit_limit: None,
data: calldata,
},
];
Specs {
actions,
differential: false,
..Default::default()
}
.run();
Ok(())
}
+38 -2
View File
@@ -1,9 +1,11 @@
use std::time::Instant;
use std::{str::FromStr, time::Instant};
use serde::{Deserialize, Serialize};
use crate::*;
use alloy_primitives::{keccak256, Address};
use alloy_primitives::keccak256;
#[cfg(feature = "revive-solidity")]
use alloy_primitives::Address;
#[cfg(feature = "revive-solidity")]
use revive_differential::{Evm, EvmLog};
#[cfg(feature = "revive-solidity")]
@@ -156,6 +158,39 @@ impl TestAddress {
}
}
impl FromStr for TestAddress {
type Err = &'static str;
fn from_str(value: &str) -> Result<Self, Self::Err> {
value.try_into()
}
}
impl TryFrom<&str> for TestAddress {
type Error = &'static str;
fn try_from(value: &str) -> Result<Self, Self::Error> {
match value {
"alice" => Ok(Self::Alice),
"bob" => Ok(Self::Bob),
"charlie" => Ok(Self::Charlie),
value => {
if let Ok(value) = value.parse() {
return Ok(Self::Instantiated(value));
}
if let Ok(value) = hex::decode(value) {
if value.len() == 20 {
return Ok(Self::AccountId(H160(value.try_into().unwrap())));
}
}
Err("can not parse into test address")
}
}
}
}
/// Specs for a contract test
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(default)]
@@ -212,6 +247,7 @@ impl Specs {
/// Helper to allow not specifying the code bytes or path directly in the runner.json
/// - Replace `Code::Bytes(bytes)` if `bytes` are empty: read `contract_file`
/// - Replace `Code::Solidity{ path, ..}` if `path` is not provided: replace `path` with `contract_file`
#[allow(unused_variables)]
pub fn replace_empty_code(&mut self, contract_name: &str, contract_path: &str) {
for action in self.actions.iter_mut() {
let code = match action {
-28
View File
@@ -5,34 +5,6 @@
// Missing builtins
#define EVM_WORD_SIZE 32
#define ALIGN(size) ((size + EVM_WORD_SIZE - 1) & ~(EVM_WORD_SIZE - 1))
#define MAX_MEMORY_SIZE (64 * 1024)
char __memory[MAX_MEMORY_SIZE];
uint32_t __memory_size = 0;
void * __sbrk_internal(uint32_t offset, uint32_t size) {
if (offset >= MAX_MEMORY_SIZE || size > MAX_MEMORY_SIZE) {
POLKAVM_TRAP();
}
uint32_t new_size = ALIGN(offset + size);
if (new_size > MAX_MEMORY_SIZE) {
POLKAVM_TRAP();
}
if (new_size > __memory_size) {
__memory_size = new_size;
}
return (void *)&__memory[offset];
}
void * memset(void *b, int c, size_t len) {
uint8_t *dest = b;
while (len-- > 0) *dest++ = c;
return b;
}
void * memcpy(void *dst, const void *_src, size_t len) {
uint8_t *dest = dst;
const uint8_t *src = _src;
+1 -10
View File
@@ -2,14 +2,6 @@ use inkwell::{context::Context, memory_buffer::MemoryBuffer, module::Module, sup
include!(concat!(env!("OUT_DIR"), "/polkavm_imports.rs"));
/// The emulated EVM heap memory global symbol.
pub static MEMORY: &str = "__memory";
/// The emulated EVM heap memory size global symbol.
pub static MEMORY_SIZE: &str = "__memory_size";
pub static SBRK: &str = "__sbrk_internal";
pub static ADDRESS: &str = "address";
pub static BALANCE: &str = "balance";
@@ -78,8 +70,7 @@ pub static WEIGHT_TO_FEE: &str = "weight_to_fee";
/// All imported runtime API symbols.
/// Useful for configuring common attributes and linkage.
pub static IMPORTS: [&str; 34] = [
SBRK,
pub static IMPORTS: [&str; 33] = [
ADDRESS,
BALANCE,
BALANCE_OF,
+11
View File
@@ -55,6 +55,7 @@ pub fn yul<T: Compiler>(
include_metadata_hash: bool,
debug_config: revive_llvm_context::DebugConfig,
llvm_arguments: &[String],
memory_config: revive_llvm_context::MemoryConfig,
) -> anyhow::Result<Build> {
let path = match input_files.len() {
1 => input_files.first().expect("Always exists"),
@@ -80,6 +81,7 @@ pub fn yul<T: Compiler>(
include_metadata_hash,
debug_config,
llvm_arguments,
memory_config,
)?;
Ok(build)
@@ -92,6 +94,7 @@ pub fn llvm_ir(
include_metadata_hash: bool,
debug_config: revive_llvm_context::DebugConfig,
llvm_arguments: &[String],
memory_config: revive_llvm_context::MemoryConfig,
) -> anyhow::Result<Build> {
let path = match input_files.len() {
1 => input_files.first().expect("Always exists"),
@@ -109,6 +112,7 @@ pub fn llvm_ir(
include_metadata_hash,
debug_config,
llvm_arguments,
memory_config,
)?;
Ok(build)
@@ -131,6 +135,7 @@ pub fn standard_output<T: Compiler>(
suppressed_warnings: Option<Vec<ResolcWarning>>,
debug_config: revive_llvm_context::DebugConfig,
llvm_arguments: &[String],
memory_config: revive_llvm_context::MemoryConfig,
) -> anyhow::Result<Build> {
let solc_version = solc.version()?;
@@ -189,12 +194,14 @@ pub fn standard_output<T: Compiler>(
include_metadata_hash,
debug_config,
llvm_arguments,
memory_config,
)?;
Ok(build)
}
/// Runs the standard JSON mode.
#[allow(clippy::too_many_arguments)]
pub fn standard_json<T: Compiler>(
solc: &mut T,
detect_missing_libraries: bool,
@@ -203,6 +210,7 @@ pub fn standard_json<T: Compiler>(
allow_paths: Option<String>,
debug_config: revive_llvm_context::DebugConfig,
llvm_arguments: &[String],
memory_config: revive_llvm_context::MemoryConfig,
) -> anyhow::Result<()> {
let solc_version = solc.version()?;
@@ -250,6 +258,7 @@ pub fn standard_json<T: Compiler>(
include_metadata_hash,
debug_config,
llvm_arguments,
memory_config,
)?;
build.write_to_standard_json(&mut solc_output, &solc_version)?;
}
@@ -277,6 +286,7 @@ pub fn combined_json<T: Compiler>(
output_directory: Option<PathBuf>,
overwrite: bool,
llvm_arguments: &[String],
memory_config: revive_llvm_context::MemoryConfig,
) -> anyhow::Result<()> {
let build = standard_output(
input_files,
@@ -293,6 +303,7 @@ pub fn combined_json<T: Compiler>(
suppressed_warnings,
debug_config,
llvm_arguments,
memory_config,
)?;
let mut combined_json = solc.combined_json(input_files, format.as_str())?;
+4
View File
@@ -22,6 +22,8 @@ pub struct Input {
pub debug_config: revive_llvm_context::DebugConfig,
/// The extra LLVM arguments give used for manual control.
pub llvm_arguments: Vec<String>,
/// The PVM memory configuration.
pub memory_config: revive_llvm_context::MemoryConfig,
}
impl Input {
@@ -33,6 +35,7 @@ impl Input {
optimizer_settings: revive_llvm_context::OptimizerSettings,
debug_config: revive_llvm_context::DebugConfig,
llvm_arguments: Vec<String>,
memory_config: revive_llvm_context::MemoryConfig,
) -> Self {
Self {
contract,
@@ -41,6 +44,7 @@ impl Input {
optimizer_settings,
debug_config,
llvm_arguments,
memory_config,
}
}
}
+1
View File
@@ -50,6 +50,7 @@ pub trait Process {
input.include_metadata_hash,
input.debug_config,
&input.llvm_arguments,
input.memory_config,
);
match result {
@@ -78,6 +78,7 @@ impl Contract {
include_metadata_hash: bool,
debug_config: revive_llvm_context::DebugConfig,
llvm_arguments: &[String],
memory_config: revive_llvm_context::MemoryConfig,
) -> anyhow::Result<ContractBuild> {
let llvm = inkwell::context::Context::create();
let optimizer = revive_llvm_context::Optimizer::new(optimizer_settings);
@@ -123,6 +124,7 @@ impl Contract {
include_metadata_hash,
debug_config,
llvm_arguments,
memory_config,
);
context.set_solidity_data(revive_llvm_context::PolkaVMContextSolidityData::default());
match self.ir {
+4
View File
@@ -67,6 +67,7 @@ impl Project {
include_metadata_hash: bool,
debug_config: revive_llvm_context::DebugConfig,
llvm_arguments: &[String],
memory_config: revive_llvm_context::MemoryConfig,
) -> anyhow::Result<Build> {
let project = self.clone();
#[cfg(feature = "parallel")]
@@ -83,6 +84,7 @@ impl Project {
optimizer_settings.clone(),
debug_config.clone(),
llvm_arguments.to_vec(),
memory_config,
);
let process_output = {
#[cfg(target_os = "emscripten")]
@@ -319,6 +321,7 @@ impl revive_llvm_context::PolkaVMDependency for Project {
include_metadata_hash: bool,
debug_config: revive_llvm_context::DebugConfig,
llvm_arguments: &[String],
memory_config: revive_llvm_context::MemoryConfig,
) -> anyhow::Result<String> {
let contract_path = project.resolve_path(identifier)?;
let contract = project
@@ -339,6 +342,7 @@ impl revive_llvm_context::PolkaVMDependency for Project {
include_metadata_hash,
debug_config,
llvm_arguments,
memory_config,
)
.map_err(|error| {
anyhow::anyhow!(
+33 -1
View File
@@ -167,9 +167,41 @@ pub struct Arguments {
#[arg(long = "recursive-process-input")]
pub recursive_process_input: Option<String>,
#[arg(long = "llvm-arg")]
/// These are passed to LLVM as the command line to allow manual control.
#[arg(long = "llvm-arg")]
pub llvm_arguments: Vec<String>,
/// The emulated EVM linear heap memory static buffer size in bytes.
///
/// Unlike the EVM, due to the lack of dynamic memory metering, PVM contracts emulate
/// the EVM heap memory with a static buffer. Consequentially, instead of infinite
/// memory with exponentially growing gas costs, PVM contracts have a finite amount
/// of memory with constant gas costs available.
///
/// If the contract uses more heap memory than configured, it will compile fine but
/// eventually revert execution at runtime!
///
/// You are incentiviced to keep this value as small as possible:
/// 1.Increasing the heap size will increase startup costs.
/// 2.The heap size contributes to the total memory size a contract can use,
/// which includes the contracts code size
#[arg(long = "heap-size", default_value = "65536")]
pub heap_size: u32,
/// The contracts total stack size in bytes.
///
/// PVM is a register machine with a traditional stack memory space for local
/// variables. This controls the total amount of stack space the contract can use.
///
/// If the contract uses more stack memory than configured, it will compile fine but
/// eventually revert execution at runtime!
///
/// You are incentiviced to keep this value as small as possible:
/// 1.Increasing the heap size will increase startup costs.
/// 2.The stack size contributes to the total memory size a contract can use,
/// which includes the contracts code size
#[arg(long = "stack-size", default_value = "32768")]
pub stack_size: u32,
}
impl Arguments {
+10
View File
@@ -148,6 +148,11 @@ fn main_inner() -> anyhow::Result<()> {
None => true,
};
let memory_config = revive_llvm_context::MemoryConfig {
heap_size: arguments.heap_size,
stack_size: arguments.stack_size,
};
let build = if arguments.yul {
revive_solidity::yul(
input_files.as_slice(),
@@ -156,6 +161,7 @@ fn main_inner() -> anyhow::Result<()> {
include_metadata_hash,
debug_config,
&arguments.llvm_arguments,
memory_config,
)
} else if arguments.llvm_ir {
revive_solidity::llvm_ir(
@@ -164,6 +170,7 @@ fn main_inner() -> anyhow::Result<()> {
include_metadata_hash,
debug_config,
&arguments.llvm_arguments,
memory_config,
)
} else if arguments.standard_json {
revive_solidity::standard_json(
@@ -174,6 +181,7 @@ fn main_inner() -> anyhow::Result<()> {
arguments.allow_paths,
debug_config,
&arguments.llvm_arguments,
memory_config,
)?;
return Ok(());
} else if let Some(format) = arguments.combined_json {
@@ -195,6 +203,7 @@ fn main_inner() -> anyhow::Result<()> {
arguments.output_directory,
arguments.overwrite,
&arguments.llvm_arguments,
memory_config,
)?;
return Ok(());
} else {
@@ -213,6 +222,7 @@ fn main_inner() -> anyhow::Result<()> {
suppressed_warnings,
debug_config,
&arguments.llvm_arguments,
memory_config,
)
}?;
+14 -3
View File
@@ -115,8 +115,13 @@ pub fn build_solidity_with_options(
&debug_config,
)?;
let build: crate::Build =
project.compile(optimizer_settings, false, debug_config, Default::default())?;
let build: crate::Build = project.compile(
optimizer_settings,
false,
debug_config,
Default::default(),
Default::default(),
)?;
build.write_to_standard_json(&mut output, &solc_version)?;
Ok(output)
@@ -243,7 +248,13 @@ pub fn build_yul(source_code: &str) -> anyhow::Result<()> {
source_code,
None,
)?;
let _build = project.compile(optimizer_settings, false, DEBUG_CONFIG, Default::default())?;
let _build = project.compile(
optimizer_settings,
false,
DEBUG_CONFIG,
Default::default(),
Default::default(),
)?;
Ok(())
}
@@ -139,11 +139,11 @@ where
identifier.inner,
)
})?;
context.build_store(pointer, value.to_value(context)?)?;
context.build_store(pointer, value.access(context)?)?;
return Ok(());
}
let value = value.to_value(context)?;
let value = value.access(context)?;
let llvm_type = value.into_struct_value().get_type();
let tuple_pointer = context.build_alloca(llvm_type, "assignment_pointer");
context.build_store(tuple_pointer, value)?;
@@ -131,7 +131,7 @@ impl FunctionCall {
let value = argument
.into_llvm(context)?
.expect("Always exists")
.to_value(context)?;
.access(context)?;
values.push(value);
}
values.reverse();
@@ -510,7 +510,7 @@ impl FunctionCall {
let offset = context.solidity_mut().allocate_immutable(key.as_str())
/ revive_common::BYTE_LENGTH_WORD;
let index = context.xlen_type().const_int(offset as u64, false);
let value = arguments[2].to_value(context)?.into_int_value();
let value = arguments[2].access(context)?.into_int_value();
revive_llvm_context::polkavm_evm_immutable::store(context, index, value)
.map(|_| None)
}
@@ -716,16 +716,15 @@ impl FunctionCall {
Name::Call => {
let arguments = self.pop_arguments::<D, 7>(context)?;
let gas = &arguments[0];
let address = &arguments[1];
let value = &arguments[2];
let input_offset = &arguments[3];
let input_size = &arguments[4];
let output_offset = &arguments[5];
let output_size = &arguments[6];
let gas = arguments[0].access(context)?.into_int_value();
let address = arguments[1].access(context)?.into_int_value();
let value = arguments[2].access(context)?.into_int_value();
let input_offset = arguments[3].access(context)?.into_int_value();
let input_size = arguments[4].access(context)?.into_int_value();
let output_offset = arguments[5].access(context)?.into_int_value();
let output_size = arguments[6].access(context)?.into_int_value();
let simulation_address: Vec<Option<num::BigUint>> = arguments
.clone()
.into_iter()
.map(|mut argument| argument.constant.take())
.collect();
@@ -747,15 +746,14 @@ impl FunctionCall {
Name::StaticCall => {
let arguments = self.pop_arguments::<D, 6>(context)?;
let gas = &arguments[0];
let address = &arguments[1];
let input_offset = &arguments[2];
let input_size = &arguments[3];
let output_offset = &arguments[4];
let output_size = &arguments[5];
let gas = arguments[0].access(context)?.into_int_value();
let address = arguments[1].access(context)?.into_int_value();
let input_offset = arguments[2].access(context)?.into_int_value();
let input_size = arguments[3].access(context)?.into_int_value();
let output_offset = arguments[4].access(context)?.into_int_value();
let output_size = arguments[5].access(context)?.into_int_value();
let simulation_address: Vec<Option<num::BigUint>> = arguments
.clone()
.into_iter()
.map(|mut argument| argument.constant.take())
.collect();
@@ -777,12 +775,12 @@ impl FunctionCall {
Name::DelegateCall => {
let arguments = self.pop_arguments::<D, 6>(context)?;
let gas = arguments[0].to_value(context)?.into_int_value();
let address = arguments[1].to_value(context)?.into_int_value();
let input_offset = arguments[2].to_value(context)?.into_int_value();
let input_size = arguments[3].to_value(context)?.into_int_value();
let output_offset = arguments[4].to_value(context)?.into_int_value();
let output_size = arguments[5].to_value(context)?.into_int_value();
let gas = arguments[0].access(context)?.into_int_value();
let address = arguments[1].access(context)?.into_int_value();
let input_offset = arguments[2].access(context)?.into_int_value();
let input_size = arguments[3].access(context)?.into_int_value();
let output_offset = arguments[4].access(context)?.into_int_value();
let output_size = arguments[5].access(context)?.into_int_value();
let simulation_address: Vec<Option<num::BigUint>> = arguments
.into_iter()
@@ -843,7 +841,7 @@ impl FunctionCall {
})?;
revive_llvm_context::polkavm_evm_create::contract_hash(context, identifier)
.and_then(|argument| argument.to_value(context))
.and_then(|argument| argument.access(context))
.map(Some)
}
Name::DataSize => {
@@ -854,7 +852,7 @@ impl FunctionCall {
})?;
revive_llvm_context::polkavm_evm_create::header_size(context, identifier)
.and_then(|argument| argument.to_value(context))
.and_then(|argument| argument.access(context))
.map(Some)
}
Name::DataCopy => {
@@ -993,7 +991,7 @@ impl FunctionCall {
expression
.into_llvm(context)?
.expect("Always exists")
.to_value(context)?,
.access(context)?,
);
}
arguments.reverse();
@@ -78,7 +78,7 @@ where
.condition
.into_llvm(context)?
.expect("Always exists")
.to_value(context)?
.access(context)?
.into_int_value();
let condition = context.builder().build_int_z_extend_or_bit_cast(
condition,
@@ -57,7 +57,7 @@ where
.condition
.into_llvm(context)?
.expect("Always exists")
.to_value(context)?
.access(context)?
.into_int_value();
let condition = context.builder().build_int_z_extend_or_bit_cast(
condition,
@@ -188,9 +188,6 @@ where
revive_llvm_context::PolkaVMLoadImmutableDataFunction.declare(context)?;
revive_llvm_context::PolkaVMStoreImmutableDataFunction.declare(context)?;
revive_llvm_context::PolkaVMCallFunction.declare(context)?;
revive_llvm_context::PolkaVMCallReentrancyHeuristicFunction.declare(context)?;
revive_llvm_context::PolkaVMLoadHeapWordFunction.declare(context)?;
revive_llvm_context::PolkaVMStoreHeapWordFunction.declare(context)?;
revive_llvm_context::PolkaVMLoadStorageWordFunction.declare(context)?;
@@ -212,6 +209,8 @@ where
revive_llvm_context::PolkaVMRemainderFunction.declare(context)?;
revive_llvm_context::PolkaVMSignedRemainderFunction.declare(context)?;
revive_llvm_context::PolkaVMSbrkFunction.declare(context)?;
let mut entry = revive_llvm_context::PolkaVMEntryFunction::default();
entry.declare(context)?;
@@ -243,9 +242,6 @@ where
revive_llvm_context::PolkaVMLoadImmutableDataFunction.into_llvm(context)?;
revive_llvm_context::PolkaVMStoreImmutableDataFunction.into_llvm(context)?;
revive_llvm_context::PolkaVMCallFunction.into_llvm(context)?;
revive_llvm_context::PolkaVMCallReentrancyHeuristicFunction.into_llvm(context)?;
revive_llvm_context::PolkaVMLoadHeapWordFunction.into_llvm(context)?;
revive_llvm_context::PolkaVMStoreHeapWordFunction.into_llvm(context)?;
revive_llvm_context::PolkaVMLoadStorageWordFunction.into_llvm(context)?;
@@ -267,6 +263,8 @@ where
revive_llvm_context::PolkaVMRemainderFunction.into_llvm(context)?;
revive_llvm_context::PolkaVMSignedRemainderFunction.into_llvm(context)?;
revive_llvm_context::PolkaVMSbrkFunction.into_llvm(context)?;
Ok(())
}
@@ -137,7 +137,7 @@ where
let mut branches = Vec::with_capacity(self.cases.len());
for (index, case) in self.cases.into_iter().enumerate() {
let constant = case.literal.into_llvm(context)?.to_value(context)?;
let constant = case.literal.into_llvm(context)?.access(context)?;
let expression_block = context
.append_basic_block(format!("switch_case_branch_{}_block", index + 1).as_str());
@@ -163,7 +163,7 @@ where
context.builder().build_switch(
scrutinee
.expect("Always exists")
.to_value(context)?
.access(context)?
.into_int_value(),
default_block,
branches.as_slice(),
@@ -121,7 +121,7 @@ where
.insert_constant(identifier.inner.clone(), constant);
}
value.to_value(context)?
value.access(context)?
}
None => r#type.const_zero().as_basic_value_enum(),
}
@@ -175,7 +175,7 @@ where
.collect::<Vec<inkwell::types::BasicTypeEnum<'ctx>>>()
.as_slice(),
);
let value = expression.to_value(context)?;
let value = expression.access(context)?;
if value.get_type() != llvm_type.as_basic_type_enum() {
anyhow::bail!(
"{} Assignment to {:?} received an invalid number of arguments",
+11
View File
@@ -0,0 +1,11 @@
import globals from 'globals'
import pluginJs from '@eslint/js'
import tseslint from 'typescript-eslint'
/** @type {import('eslint').Linter.Config[]} */
export default [
{ files: ['**/*.{mjs,ts}'] },
{ languageOptions: { globals: globals.browser } },
pluginJs.configs.recommended,
...tseslint.configs.recommended,
]
+1 -1
View File
@@ -8,7 +8,7 @@ const RESOLC_WASM_URI =
process.env.RELEASE_RESOLC_WASM_URI || "http://127.0.0.1:8080/resolc.wasm";
const RESOLC_WASM_TARGET_DIR = path.join(
__dirname,
"../target/wasm32-unknown-emscripten/release",
"../../target/wasm32-unknown-emscripten/release",
);
const RESOLC_JS = path.join(RESOLC_WASM_TARGET_DIR, "resolc.js");
const RESOLC_WEB_JS = path.join(RESOLC_WASM_TARGET_DIR, "resolc_web.js");
+1
View File
@@ -0,0 +1 @@
../../../../target/wasm32-unknown-emscripten/release/resolc.js
+1
View File
@@ -0,0 +1 @@
../../../../target/wasm32-unknown-emscripten/release/resolc.wasm
+1
View File
@@ -0,0 +1 @@
../../../../target/wasm32-unknown-emscripten/release/resolc.wasm
+1
View File
@@ -0,0 +1 @@
../../../../target/wasm32-unknown-emscripten/release/resolc_web.js
+19
View File
@@ -0,0 +1,19 @@
{
"language": "Solidity",
"sources": {
"fixtures/instantiate.sol": {
"content": "// SPDX-License-Identifier: GPL-3.0\npragma solidity >=0.8.2 <0.9.0;\ncontract ChildContract {\n constructor() {\n }\n}\ncontract MainContract {\n constructor() {\n ChildContract newContract = new ChildContract();\n }\n}"
}
},
"settings": {
"optimizer": {
"enabled": true,
"runs": 200
},
"outputSelection": {
"*": {
"*": ["abi"]
}
}
}
}
@@ -72,9 +72,7 @@
},
"outputSelection": {
"*": {
"*": [
"abi"
]
"*": ["abi"]
}
}
}
@@ -65,15 +65,15 @@ describe("Compile Function Tests", function () {
expect(result).to.be.a("string");
const output = JSON.parse(result);
expect(output).to.have.property("contracts");
expect(output.contracts["fixtures/instantiate_tokens.sol"]).to.have.property(
"TokensFactory",
);
expect(output.contracts["fixtures/instantiate_tokens.sol"].TokensFactory).to.have.property(
"abi",
);
expect(output.contracts["fixtures/instantiate_tokens.sol"].TokensFactory).to.have.property(
"evm",
);
expect(
output.contracts["fixtures/instantiate_tokens.sol"],
).to.have.property("TokensFactory");
expect(
output.contracts["fixtures/instantiate_tokens.sol"].TokensFactory,
).to.have.property("abi");
expect(
output.contracts["fixtures/instantiate_tokens.sol"].TokensFactory,
).to.have.property("evm");
expect(
output.contracts["fixtures/instantiate_tokens.sol"].TokensFactory.evm,
).to.have.property("bytecode");
@@ -117,27 +117,26 @@ describe("Compile Function Tests", function () {
expect(output.contracts["fixtures/instantiate.sol"]).to.have.property(
"ChildContract",
);
expect(output.contracts["fixtures/instantiate.sol"].ChildContract).to.have.property(
"abi",
);
expect(output.contracts["fixtures/instantiate.sol"].ChildContract).to.have.property(
"evm",
);
expect(
output.contracts["fixtures/instantiate.sol"].ChildContract,
).to.have.property("abi");
expect(
output.contracts["fixtures/instantiate.sol"].ChildContract,
).to.have.property("evm");
expect(
output.contracts["fixtures/instantiate.sol"].ChildContract.evm,
).to.have.property("bytecode");
expect(output.contracts["fixtures/instantiate.sol"]).to.have.property(
"MainContract",
);
expect(output.contracts["fixtures/instantiate.sol"].MainContract).to.have.property(
"abi",
);
expect(output.contracts["fixtures/instantiate.sol"].MainContract).to.have.property(
"evm",
);
expect(
output.contracts["fixtures/instantiate.sol"].MainContract,
).to.have.property("abi");
expect(
output.contracts["fixtures/instantiate.sol"].MainContract,
).to.have.property("evm");
expect(
output.contracts["fixtures/instantiate.sol"].MainContract.evm,
).to.have.property("bytecode");
});
});
-1
View File
@@ -1 +0,0 @@
../../../target/wasm32-unknown-emscripten/release/resolc.js
-1
View File
@@ -1 +0,0 @@
../../../target/wasm32-unknown-emscripten/release/resolc.wasm
-1
View File
@@ -1 +0,0 @@
../../../target/wasm32-unknown-emscripten/release/resolc.wasm
-1
View File
@@ -1 +0,0 @@
../../../target/wasm32-unknown-emscripten/release/resolc_web.js
-20
View File
@@ -1,20 +0,0 @@
{
"language": "Solidity",
"sources": {
"fixtures/instantiate.sol": {
"content": "// SPDX-License-Identifier: GPL-3.0\npragma solidity >=0.8.2 <0.9.0;\ncontract ChildContract {\n constructor() {\n }\n}\ncontract MainContract {\n constructor() {\n ChildContract newContract = new ChildContract();\n }\n}"
}
},
"settings": {
"optimizer": {
"enabled": true,
"runs": 200
},
"outputSelection": {
"*": {
"*": ["abi"]
}
}
}
}
+17
View File
@@ -0,0 +1,17 @@
# Usage from Node.js
```typescript
import { compile } from "@parity/resolc";
const sources = {
["contracts/1_Storage.sol"]: {
content: readFileSync("fixtures/storage.sol", "utf8"),
}
const out = await compile(sources);
```
# Usage from shell
```bash
npx @parity/resolc@latest --bin contracts/1_Storage.sol
```
+10
View File
@@ -0,0 +1,10 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^9999;
/**
* @title Storage
* @dev Store & retrieve value in a variable
* @custom:dev-run-script ./scripts/deploy_with_ethers.ts
*/
contract BadPragma {
}
+28
View File
@@ -0,0 +1,28 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.2 <0.9.0;
/**
* @title Storage
* @dev Store & retrieve value in a variable
* @custom:dev-run-script ./scripts/deploy_with_ethers.ts
*/
contract Storage {
uint256 number;
/**
* @dev Store value in variable
* @param num value to store
*/
function store(uint256 num) public {
number = num;
}
/**
* @dev Return value
* @return value of 'number'
*/
function retrieve() public view returns (uint256){
return number;
}
}
+26
View File
@@ -0,0 +1,26 @@
// missing license and pragama
/**
* @title Storage
* @dev Store & retrieve value in a variable
* @custom:dev-run-script ./scripts/deploy_with_ethers.ts
*/
contract Storage {
uint256 number;
/**
* @dev Store value in variable
* @param num value to store
*/
function store(uint256 num) public {
number = num;
}
/**
* @dev Return value
* @return value of 'number'
*/
function retrieve() public view returns (uint256){
return number;
}
}
+22
View File
@@ -0,0 +1,22 @@
// SPDX-License-Identifier: MIT
// Compatible with OpenZeppelin Contracts ^5.0.0
pragma solidity ^0.8.22;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";
contract MyToken is ERC20, Ownable, ERC20Permit {
constructor(address initialOwner)
ERC20("MyToken", "MTK")
Ownable(initialOwner)
ERC20Permit("MyToken")
{
_mint(msg.sender, 100 * 10 ** decimals());
}
function mint(address to, uint256 amount) public onlyOwner {
_mint(to, amount);
}
}
+37
View File
@@ -0,0 +1,37 @@
{
"name": "@parity/resolc",
"license": "Apache-2.0",
"version": "0.0.0-updated-via-gh-releases",
"author": "Parity <admin@parity.io> (https://parity.io)",
"module": "index.ts",
"types": "./dist/index.d.ts",
"main": "./dist/index.js",
"bin": {
"resolc": "./dist/bin.js"
},
"exports": {
".": {
"import": "./dist/index.js",
"require": "./dist/index.js",
"types": "./dist/index.d.ts"
}
},
"files": [
"dist"
],
"scripts": {
"build": "tsc && cp src/resolc/** dist/resolc",
"test": "npm run build && node ./dist/index.test.js"
},
"devDependencies": {
"@openzeppelin/contracts": "5.1.0",
"globals": "^15.12.0",
"typescript": "^5.6.3"
},
"dependencies": {
"@types/node": "^22.9.0",
"commander": "^13.1.0",
"package-json": "^10.0.1",
"solc": "^0.8.29"
}
}
+203
View File
@@ -0,0 +1,203 @@
#!/usr/bin/env node
import * as commander from 'commander'
import * as fs from 'fs'
import * as os from 'os'
import * as path from 'path'
import * as resolc from '.'
import { SolcInput } from '.'
async function main() {
// hold on to any exception handlers that existed prior to this script running, we'll be adding them back at the end
const originalUncaughtExceptionListeners =
process.listeners('uncaughtException')
// FIXME: remove annoying exception catcher of Emscripten
// see https://github.com/chriseth/browser-solidity/issues/167
process.removeAllListeners('uncaughtException')
const program = new commander.Command()
program.name('solcjs')
program.version(resolc.version())
program
.option('--bin', 'Binary of the contracts in hex.')
.option('--abi', 'ABI of the contracts.')
.option(
'--base-path <path>',
'Root of the project source tree. ' +
'The import callback will attempt to interpret all import paths as relative to this directory.'
)
.option(
'--include-path <path...>',
'Extra source directories available to the import callback. ' +
'When using a package manager to install libraries, use this option to specify directories where packages are installed. ' +
'Can be used multiple times to provide multiple locations.'
)
.option(
'-o, --output-dir <output-directory>',
'Output directory for the contracts.'
)
.option('-p, --pretty-json', 'Pretty-print all JSON output.', false)
.option('-v, --verbose', 'More detailed console output.', false)
.argument('<files...>')
program.parse(process.argv)
const options = program.opts<{
verbose: boolean
abi: boolean
bin: boolean
outputDir?: string
prettyJson: boolean
basePath?: string
includePath?: string[]
}>()
const files: string[] = program.args
const destination = options.outputDir ?? '.'
function abort(msg: string) {
console.error(msg || 'Error occurred')
process.exit(1)
}
function withUnixPathSeparators(filePath: string) {
// On UNIX-like systems forward slashes in paths are just a part of the file name.
if (os.platform() !== 'win32') {
return filePath
}
return filePath.replace(/\\/g, '/')
}
function makeSourcePathRelativeIfPossible(sourcePath: string) {
const absoluteBasePath = options.basePath
? path.resolve(options.basePath)
: path.resolve('.')
const absoluteIncludePaths = options.includePath
? options.includePath.map((prefix: string) => {
return path.resolve(prefix)
})
: []
// Compared to base path stripping logic in solc this is much simpler because path.resolve()
// handles symlinks correctly (does not resolve them except in work dir) and strips .. segments
// from paths going beyond root (e.g. `/../../a/b/c` -> `/a/b/c/`). It's simpler also because it
// ignores less important corner cases: drive letters are not stripped from absolute paths on
// Windows and UNC paths are not handled in a special way (at least on Linux). Finally, it has
// very little test coverage so there might be more differences that we are just not aware of.
const absoluteSourcePath = path.resolve(sourcePath)
for (const absolutePrefix of [absoluteBasePath].concat(
absoluteIncludePaths
)) {
const relativeSourcePath = path.relative(
absolutePrefix,
absoluteSourcePath
)
if (!relativeSourcePath.startsWith('../')) {
return withUnixPathSeparators(relativeSourcePath)
}
}
// File is not located inside base path or include paths so use its absolute path.
return withUnixPathSeparators(absoluteSourcePath)
}
function toFormattedJson<T>(input: T) {
return JSON.stringify(input, null, options.prettyJson ? 4 : 0)
}
if (files.length === 0) {
console.error('Must provide a file')
process.exit(1)
}
if (!(options.bin || options.abi)) {
abort('Invalid option selected, must specify either --bin or --abi')
}
const sources: SolcInput = {}
for (let i = 0; i < files.length; i++) {
try {
sources[makeSourcePathRelativeIfPossible(files[i])] = {
content: fs.readFileSync(files[i]).toString(),
}
} catch (e) {
abort('Error reading ' + files[i] + ': ' + e)
}
}
if (options.verbose) {
console.log('>>> Compiling:\n' + toFormattedJson(sources) + '\n')
}
const output = await resolc.compile(sources)
let hasError = false
if (!output) {
abort('No output from compiler')
} else if (output.errors) {
for (const error in output.errors) {
const message = output.errors[error]
if (message.severity === 'warning') {
console.log(message.formattedMessage)
} else {
console.error(message.formattedMessage)
hasError = true
}
}
}
fs.mkdirSync(destination, { recursive: true })
function writeFile(file: string, content: Buffer | string) {
file = path.join(destination, file)
fs.writeFile(file, content, function (err) {
if (err) {
console.error('Failed to write ' + file + ': ' + err)
}
})
}
for (const fileName in output.contracts) {
for (const contractName in output.contracts[fileName]) {
let contractFileName = fileName + ':' + contractName
contractFileName = contractFileName.replace(/[:./\\]/g, '_')
if (options.bin) {
writeFile(
contractFileName + '.polkavm',
Buffer.from(
output.contracts[fileName][contractName].evm.bytecode
.object,
'hex'
)
)
}
if (options.abi) {
writeFile(
contractFileName + '.abi',
toFormattedJson(
output.contracts[fileName][contractName].abi
)
)
}
}
}
// Put back original exception handlers.
originalUncaughtExceptionListeners.forEach(function (listener) {
process.addListener('uncaughtException', listener)
})
if (hasError) {
process.exit(1)
}
}
main().catch((err) => {
console.error('Error:', err)
process.exit(1)
})
+115
View File
@@ -0,0 +1,115 @@
import { test } from 'node:test'
import { readFileSync, existsSync } from 'node:fs'
import assert from 'node:assert'
import { compile, tryResolveImport } from '.'
import { resolve } from 'node:path'
const compileOptions = [{}]
if (existsSync('../../target/release/resolc')) {
compileOptions.push({ bin: '../../target/release/resolc' })
}
for (const options of compileOptions) {
test(`check Ok output with option ${JSON.stringify(options)}`, async () => {
const contract = 'fixtures/token.sol'
const sources = {
[contract]: {
content: readFileSync('fixtures/storage.sol', 'utf8'),
},
}
const out = await compile(sources, options)
assert(out.contracts[contract].Storage.abi != null)
assert(out.contracts[contract].Storage.evm.bytecode != null)
})
}
test('check Err output', async () => {
const sources = {
bad: {
content: readFileSync('fixtures/storage_bad.sol', 'utf8'),
},
}
const out = await compile(sources)
assert(
out?.errors?.[0].message.includes(
'SPDX license identifier not provided in source file'
)
)
assert(
out?.errors?.[1].message.includes(
'Source file does not specify required compiler version'
)
)
})
test('check Err from stderr', async () => {
const sources = {
bad: {
content: readFileSync('fixtures/bad_pragma.sol', 'utf8'),
},
}
try {
await compile(sources)
assert(false, 'Expected error')
} catch (error) {
assert(
String(error).includes(
'Source file requires different compiler version'
)
)
}
})
test('resolve import', () => {
const cases = [
// local
{
file: './fixtures/storage.sol',
expected: resolve('fixtures/storage.sol'),
},
// scopped module with version
{
file: '@openzeppelin/contracts@5.1.0/token/ERC20/ERC20.sol',
expected: require.resolve('@openzeppelin/contracts/token/ERC20/ERC20.sol'),
},
// scopped module without version
{
file: '@openzeppelin/contracts/token/ERC20/ERC20.sol',
expected: require.resolve('@openzeppelin/contracts/token/ERC20/ERC20.sol'),
},
// scopped module with wrong version
{
file: '@openzeppelin/contracts@4.8.3/token/ERC20/ERC20.sol',
expected: `Error: Version mismatch: Specified @openzeppelin/contracts@4.8.3, but installed version is 5.1.0`,
},
// module without version
{
file: '@openzeppelin/contracts/package.json',
expected: require.resolve('@openzeppelin/contracts/package.json'),
},
// scopped module with version
{
file: '@openzeppelin/contracts@5.1.0/package.json',
expected: require.resolve('@openzeppelin/contracts/package.json'),
},
]
for (const { file, expected } of cases) {
try {
const resolved = tryResolveImport(file)
assert(
resolved === expected,
`\nGot:\n${resolved}\nExpected:\n${expected}`
)
} catch (error) {
assert(
String(error) == expected,
`\nGot:\n${String(error)}\nExpected:\n${expected}`
)
}
}
})
+202
View File
@@ -0,0 +1,202 @@
import solc from 'solc'
import { spawn } from 'child_process'
import { resolc, version as resolcVersion } from './resolc'
import path from 'path'
import { existsSync, readFileSync } from 'fs'
export type SolcInput = {
[contractName: string]: {
content: string
}
}
export type SolcError = {
component: string
errorCode: string
formattedMessage: string
message: string
severity: string
sourceLocation?: {
file: string
start: number
end: number
}
type: string
}
export type SolcOutput = {
contracts: {
[contractPath: string]: {
[contractName: string]: {
abi: Array<{
name: string
inputs: Array<{ name: string; type: string }>
outputs: Array<{ name: string; type: string }>
stateMutability: string
type: string
}>
evm: {
bytecode: { object: string }
}
}
}
}
errors?: Array<SolcError>
}
export function resolveInputs(sources: SolcInput): SolcInput {
const input = {
language: 'Solidity',
sources,
settings: {
outputSelection: {
'*': {
'*': ['evm.bytecode.object'],
},
},
},
}
const out = solc.compile(JSON.stringify(input), {
import: (path: string) => {
return {
contents: readFileSync(tryResolveImport(path), 'utf8'),
}
},
})
const output = JSON.parse(out) as {
sources: { [fileName: string]: { id: number } }
errors: Array<SolcError>
}
if (output.errors && Object.keys(output.sources).length === 0) {
throw new Error(output.errors[0].formattedMessage)
}
return Object.fromEntries(
Object.keys(output.sources).map((fileName) => {
return [
fileName,
sources[fileName] ?? {
content: readFileSync(tryResolveImport(fileName), 'utf8'),
},
]
})
)
}
export function version(): string {
const v = resolcVersion()
return v.split(' ').pop() ?? v
}
export async function compile(
sources: SolcInput,
option: { bin?: string } = {}
): Promise<SolcOutput> {
const input = JSON.stringify({
language: 'Solidity',
sources: resolveInputs(sources),
settings: {
optimizer: { enabled: true, runs: 200 },
outputSelection: {
'*': {
'*': ['abi'],
},
},
},
})
if (option.bin) {
return compileWithBin(input, option.bin)
}
return resolc(input)
}
/**
* Resolve an import path to a file path.
* @param importPath - The import path to resolve.
*/
export function tryResolveImport(importPath: string) {
// resolve local path
if (existsSync(importPath)) {
return path.resolve(importPath)
}
const importRegex = /^(@?[^@/]+(?:\/[^@/]+)?)(?:@([^/]+))?(\/.+)$/
const match = importPath.match(importRegex)
if (!match) {
throw new Error('Invalid import path format.')
}
const basePackage = match[1] // "foo", "@scope/foo"
const specifiedVersion = match[2] // "1.2.3" (optional)
const relativePath = match[3] // "/path/to/file.sol"
let packageJsonPath
try {
packageJsonPath = require.resolve(
path.join(basePackage, 'package.json')
)
} catch {
throw new Error(`Could not resolve package ${basePackage}`)
}
// Check if a version was specified and compare with the installed version
if (specifiedVersion) {
const installedVersion = JSON.parse(
readFileSync(packageJsonPath, 'utf-8')
).version
if (installedVersion !== specifiedVersion) {
throw new Error(
`Version mismatch: Specified ${basePackage}@${specifiedVersion}, but installed version is ${installedVersion}`
)
}
}
const packageRoot = path.dirname(packageJsonPath)
// Construct full path to the requested file
const resolvedPath = path.join(packageRoot, relativePath)
if (existsSync(resolvedPath)) {
return resolvedPath
} else {
throw new Error(`Resolved path ${resolvedPath} does not exist.`)
}
}
function compileWithBin(input: string, bin: string): PromiseLike<SolcOutput> {
return new Promise((resolve, reject) => {
const process = spawn(bin, ['--standard-json'])
let output = ''
let error = ''
process.stdin.write(input)
process.stdin.end()
process.stdout.on('data', (data) => {
output += data.toString()
})
process.stderr.on('data', (data) => {
error += data.toString()
})
process.on('close', (code) => {
if (code === 0) {
try {
const result: SolcOutput = JSON.parse(output)
resolve(result)
} catch {
reject(new Error(`Failed to parse output`))
}
} else {
reject(new Error(`Process exited with code ${code}: ${error}`))
}
})
})
}
+30
View File
@@ -0,0 +1,30 @@
import soljson from 'solc/soljson'
import Resolc from './resolc/resolc'
import type { SolcOutput } from '.'
export function resolc(input: string): SolcOutput {
const m = Resolc() as any // eslint-disable-line @typescript-eslint/no-explicit-any
m.soljson = soljson
m.writeToStdin(input)
m.callMain(['--standard-json'])
const err = m.readFromStderr()
if (err) {
throw new Error(err)
}
return JSON.parse(m.readFromStdout()) as SolcOutput
}
export function version(): string {
const m = Resolc() as any // eslint-disable-line @typescript-eslint/no-explicit-any
m.soljson = soljson
m.callMain(['--version'])
const err = m.readFromStderr()
if (err) {
throw new Error(err)
}
return m.readFromStdout()
}
View File
+1
View File
@@ -0,0 +1 @@
../../../../target/wasm32-unknown-emscripten/release/resolc.js
+1
View File
@@ -0,0 +1 @@
../../../../target/wasm32-unknown-emscripten/release/resolc.wasm
+95
View File
@@ -0,0 +1,95 @@
declare module 'solc/soljson' {}
declare module 'solc' {
// Basic types for input/output handling
export interface CompileInput {
language: string
sources: {
[fileName: string]: {
content: string
}
}
settings?: {
optimizer?: {
enabled: boolean
runs: number
}
outputSelection: {
[fileName: string]: {
[contractName: string]: string[]
}
}
}
}
export interface CompileOutput {
errors?: Array<{
component: string
errorCode: string
formattedMessage: string
message: string
severity: string
sourceLocation?: {
file: string
start: number
end: number
}
type: string
}>
sources?: {
[fileName: string]: {
id: number
ast: object
}
}
contracts?: {
[fileName: string]: {
[contractName: string]: {
abi: object[]
evm: {
bytecode: {
object: string
sourceMap: string
linkReferences: {
[fileName: string]: {
[libraryName: string]: Array<{
start: number
length: number
}>
}
}
}
deployedBytecode: {
object: string
sourceMap: string
linkReferences: {
[fileName: string]: {
[libraryName: string]: Array<{
start: number
length: number
}>
}
}
}
}
}
}
}
}
// Main exported functions
export function compile(
input: string | CompileInput,
options?: {
import: (path: string) =>
| {
contents: string
error?: undefined
}
| {
error: string
contents?: undefined
}
}
): string
}
+27
View File
@@ -0,0 +1,27 @@
{
"compilerOptions": {
"baseUrl": ".",
"outDir": "dist",
"sourceMap": true,
"declaration": true,
"noFallthroughCasesInSwitch": true,
"allowJs": true,
"moduleResolution": "node",
"module": "commonjs",
"target": "ES2022",
"lib": ["ES2022"],
"strict": true,
"noImplicitAny": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"useUnknownInCatchVariables": true,
"types": ["node"],
"typeRoots": ["../../node_modules/@types", "./src"],
"paths": {
"#src/*": ["./src/*"]
},
"plugins": []
},
"include": ["src/**/*"]
}
+19 -11
View File
@@ -1,13 +1,21 @@
{
"name": "root",
"private": true,
"scripts": {
"test:cli": "npm run test -w crates/solidity/src/tests/cli-tests",
"test:wasm": "npm run test:node -w js",
"build:package": "npm run build:package -w js"
},
"workspaces": [
"crates/solidity/src/tests/cli-tests",
"js"
]
"name": "root",
"private": true,
"scripts": {
"test:cli": "npm run test -w crates/solidity/src/tests/cli-tests",
"test:wasm": "npm run test:node -w js/emscripten",
"build:package": "npm run build:package -w js/emscripten",
"lint": "npx eslint 'js/**/*.{cjs,mjs,ts}' && npx prettier --check '**/*.{mjs,cjs,ts}'",
"lint:fix": "npx prettier --write '**/*.{mjs,cjs,ts}'"
},
"workspaces": [
"crates/solidity/src/tests/cli-tests",
"js/emscripten",
"js/resolc"
],
"dependencies": {
"@eslint/js": "^9.14.0",
"eslint": "^9.14.0",
"typescript-eslint": "^8.13.0"
}
}