Compare commits

..

12 Commits

Author SHA1 Message Date
pgherveou 963d167fb2 Fix Set version to remove leading v 2025-05-08 12:35:31 +02:00
pgherveou df963d2795 add optimizer options: 2025-05-08 12:23:51 +02:00
pgherveou 8b1a36eddf Run lint:fix 2025-05-08 12:20:38 +02:00
xermicus 11d47d74ac apply size optimizations by default (#298)
So far if no optimization level was specified, optimizations for
execution time were applied. However, we currently are a bit limited on
code size. Add to that, this setting is not available in solc and people
generally ignore the docs, generating a lot of support requests.

Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
2025-05-07 15:35:48 +02:00
xermicus e3a9c95d32 llvm-builder: use the ninja generator for building the builtins on windows (#299)
The builtins build should use the Ninja generator (MSVC does not build a
valid archive).

Tested and verified here:
https://github.com/paritytech/revive-alex-workflowtest/releases/tag/untagged-f02d0f574bab8404fead

Closes #305

Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
2025-05-07 11:10:58 +02:00
PG Herveou a560b2d919 Fix npm-release job (#297)
follow up from #295
2025-04-30 21:48:11 +02:00
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
100 changed files with 8829 additions and 868 deletions
+2 -2
View File
@@ -10,8 +10,8 @@ rustflags = [
"-Clink-arg=-sWASM_ASYNC_COMPILATION=0", "-Clink-arg=-sWASM_ASYNC_COMPILATION=0",
"-Clink-arg=-sDYNAMIC_EXECUTION=0", "-Clink-arg=-sDYNAMIC_EXECUTION=0",
"-Clink-arg=-sALLOW_TABLE_GROWTH=1", "-Clink-arg=-sALLOW_TABLE_GROWTH=1",
"-Clink-arg=--js-library=js/embed/soljson_interface.js", "-Clink-arg=--js-library=js/emscripten/embed/soljson_interface.js",
"-Clink-arg=--pre-js=js/embed/pre.js", "-Clink-arg=--pre-js=js/emscripten/embed/pre.js",
"-Clink-arg=-sSTACK_SIZE=128kb", "-Clink-arg=-sSTACK_SIZE=128kb",
"-Clink-arg=-sNODEJS_CATCH_EXIT=0" "-Clink-arg=-sNODEJS_CATCH_EXIT=0"
] ]
+5
View File
@@ -87,6 +87,11 @@ jobs:
run: | run: |
brew install ninja brew install ninja
- name: Install Dependencies
if: ${{ matrix.host == 'windows' }}
run: |
choco install ninja
- name: Install LLVM Builder - name: Install LLVM Builder
run: | run: |
cargo install --path crates/llvm-builder cargo install --path crates/llvm-builder
+46
View File
@@ -286,3 +286,49 @@ jobs:
resolc.wasm resolc.wasm
resolc_web.js resolc_web.js
checksums.txt checksums.txt
npm-release:
needs: [create-release]
runs-on: macos-14
environment: tags
steps:
- uses: actions/checkout@v4
- name: Download Artifacts
uses: actions/download-artifact@v4
with:
merge-multiple: true
- name: Set Up Node.js
uses: actions/setup-node@v3
with:
node-version: "20"
- run: npm ci -w js/resolc
- name: Build
run: |
cp -f resolc.{wasm,js} js/resolc/src/resolc
npm -w js/resolc run build
- name: Set version
run: |
VERSION="${{ github.event.release.tag_name }}"
STRIPPED_VERSION="${VERSION#v}"
npm -w js/resolc version --no-git-tag-version "$STRIPPED_VERSION"
- name: npm pack
run: npm -w js/resolc pack
- uses: actions/upload-artifact@v4
with:
name: npm_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(''{{ "artifact_name": "npm_package", "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 - name: Install Node Packages
run: npm install run: npm install
- name: Run Playwright tests - name: Test emscripten
run: |
cd js
npx playwright install --with-deps
npx playwright test
- name: Test revive
run: | run: |
echo "Running tests for ${{ matrix.os }}" echo "Running tests for ${{ matrix.os }}"
npm run test:wasm 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 -1
View File
@@ -1,4 +1,5 @@
/target /target
/js/resolc/dist
target-llvm target-llvm
*.dot *.dot
.vscode/ .vscode/
@@ -11,7 +12,6 @@ target-llvm
node_modules node_modules
artifacts artifacts
tmp tmp
package-lock.json
/*.html /*.html
/build /build
soljson.js soljson.js
+6
View File
@@ -0,0 +1,6 @@
{
"trailingComma": "es5",
"tabWidth": 2,
"semi": false,
"singleQuote": true
}
+22
View File
@@ -6,9 +6,31 @@ This is a development pre-release.
Supported `polkadot-sdk` rev:`c29e72a8628835e34deb6aa7db9a78a2e4eabcee` Supported `polkadot-sdk` rev:`c29e72a8628835e34deb6aa7db9a78a2e4eabcee`
## v0.1.0
This is a development pre-release.
Supported `polkadot-sdk` rev:`c29e72a8628835e34deb6aa7db9a78a2e4eabcee`
### Added ### Added
### Changed ### Changed
- By default, heavy size optimizations are applied.
### 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 ### Fixed
- Constructors avoid storing zero sized immutable data on exit. - Constructors avoid storing zero sized immutable data on exit.
Generated
+18 -15
View File
@@ -4553,7 +4553,7 @@ checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104"
[[package]] [[package]]
name = "lld-sys" name = "lld-sys"
version = "0.1.0-dev.13" version = "0.1.0-dev.14"
dependencies = [ dependencies = [
"cc", "cc",
"libc", "libc",
@@ -8266,7 +8266,7 @@ dependencies = [
[[package]] [[package]]
name = "revive-benchmarks" name = "revive-benchmarks"
version = "0.1.0-dev.13" version = "0.1.0-dev.14"
dependencies = [ dependencies = [
"alloy-primitives", "alloy-primitives",
"criterion", "criterion",
@@ -8278,18 +8278,18 @@ dependencies = [
[[package]] [[package]]
name = "revive-build-utils" name = "revive-build-utils"
version = "0.1.0-dev.13" version = "0.1.0-dev.14"
[[package]] [[package]]
name = "revive-builtins" name = "revive-builtins"
version = "0.1.0-dev.13" version = "0.1.0-dev.14"
dependencies = [ dependencies = [
"revive-build-utils", "revive-build-utils",
] ]
[[package]] [[package]]
name = "revive-common" name = "revive-common"
version = "0.1.0-dev.13" version = "0.1.0-dev.14"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"serde", "serde",
@@ -8299,7 +8299,7 @@ dependencies = [
[[package]] [[package]]
name = "revive-differential" name = "revive-differential"
version = "0.1.0-dev.13" version = "0.1.0-dev.14"
dependencies = [ dependencies = [
"alloy-genesis", "alloy-genesis",
"alloy-primitives", "alloy-primitives",
@@ -8312,7 +8312,7 @@ dependencies = [
[[package]] [[package]]
name = "revive-integration" name = "revive-integration"
version = "0.1.0-dev.13" version = "0.1.0-dev.14"
dependencies = [ dependencies = [
"alloy-primitives", "alloy-primitives",
"alloy-sol-types", "alloy-sol-types",
@@ -8328,7 +8328,7 @@ dependencies = [
[[package]] [[package]]
name = "revive-linker" name = "revive-linker"
version = "0.1.0-dev.13" version = "0.1.0-dev.14"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"libc", "libc",
@@ -8340,7 +8340,7 @@ dependencies = [
[[package]] [[package]]
name = "revive-llvm-builder" name = "revive-llvm-builder"
version = "0.1.0-dev.13" version = "0.1.0-dev.14"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"assert_cmd", "assert_cmd",
@@ -8362,7 +8362,7 @@ dependencies = [
[[package]] [[package]]
name = "revive-llvm-context" name = "revive-llvm-context"
version = "0.1.0-dev.13" version = "0.1.0-dev.14"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"hex", "hex",
@@ -8384,9 +8384,12 @@ dependencies = [
[[package]] [[package]]
name = "revive-runner" name = "revive-runner"
version = "0.1.0-dev.13" version = "0.1.0-dev.14"
dependencies = [ dependencies = [
"alloy-primitives", "alloy-primitives",
"anyhow",
"clap",
"env_logger 0.11.6",
"hex", "hex",
"parity-scale-codec", "parity-scale-codec",
"polkadot-sdk 0.1.0", "polkadot-sdk 0.1.0",
@@ -8400,7 +8403,7 @@ dependencies = [
[[package]] [[package]]
name = "revive-runtime-api" name = "revive-runtime-api"
version = "0.1.0-dev.13" version = "0.1.0-dev.14"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"inkwell", "inkwell",
@@ -8410,7 +8413,7 @@ dependencies = [
[[package]] [[package]]
name = "revive-solc-json-interface" name = "revive-solc-json-interface"
version = "0.1.0-dev.13" version = "0.1.0-dev.14"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"rayon", "rayon",
@@ -8422,7 +8425,7 @@ dependencies = [
[[package]] [[package]]
name = "revive-solidity" name = "revive-solidity"
version = "0.1.0-dev.13" version = "0.1.0-dev.14"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"clap", "clap",
@@ -8449,7 +8452,7 @@ dependencies = [
[[package]] [[package]]
name = "revive-stdlib" name = "revive-stdlib"
version = "0.1.0-dev.13" version = "0.1.0-dev.14"
dependencies = [ dependencies = [
"inkwell", "inkwell",
"revive-build-utils", "revive-build-utils",
+15 -15
View File
@@ -3,7 +3,7 @@ resolver = "2"
members = ["crates/*"] members = ["crates/*"]
[workspace.package] [workspace.package]
version = "0.1.0-dev.13" version = "0.1.0-dev.14"
authors = [ authors = [
"Cyrill Leutwiler <cyrill@parity.io>", "Cyrill Leutwiler <cyrill@parity.io>",
"Parity Technologies <admin@parity.io>", "Parity Technologies <admin@parity.io>",
@@ -14,20 +14,20 @@ 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.13", path = "crates/benchmarks" } revive-benchmarks = { version = "0.1.0-dev.14", path = "crates/benchmarks" }
revive-builtins = { version = "0.1.0-dev.13", path = "crates/builtins" } revive-builtins = { version = "0.1.0-dev.14", path = "crates/builtins" }
revive-common = { version = "0.1.0-dev.13", path = "crates/common" } revive-common = { version = "0.1.0-dev.14", path = "crates/common" }
revive-differential = { version = "0.1.0-dev.13", path = "crates/differential" } revive-differential = { version = "0.1.0-dev.14", path = "crates/differential" }
revive-integration = { version = "0.1.0-dev.13", path = "crates/integration" } revive-integration = { version = "0.1.0-dev.14", path = "crates/integration" }
revive-linker = { version = "0.1.0-dev.13", path = "crates/linker" } revive-linker = { version = "0.1.0-dev.14", path = "crates/linker" }
lld-sys = { version = "0.1.0-dev.13", path = "crates/lld-sys" } lld-sys = { version = "0.1.0-dev.14", path = "crates/lld-sys" }
revive-llvm-context = { version = "0.1.0-dev.13", path = "crates/llvm-context" } revive-llvm-context = { version = "0.1.0-dev.14", path = "crates/llvm-context" }
revive-runtime-api = { version = "0.1.0-dev.13", path = "crates/runtime-api" } revive-runtime-api = { version = "0.1.0-dev.14", path = "crates/runtime-api" }
revive-runner = { version = "0.1.0-dev.13", path = "crates/runner" } revive-runner = { version = "0.1.0-dev.14", path = "crates/runner" }
revive-solc-json-interface = { version = "0.1.0-dev.13", path = "crates/solc-json-interface" } revive-solc-json-interface = { version = "0.1.0-dev.14", path = "crates/solc-json-interface" }
revive-solidity = { version = "0.1.0-dev.13", path = "crates/solidity" } revive-solidity = { version = "0.1.0-dev.14", path = "crates/solidity" }
revive-stdlib = { version = "0.1.0-dev.13", path = "crates/stdlib" } revive-stdlib = { version = "0.1.0-dev.14", path = "crates/stdlib" }
revive-build-utils = { version = "0.1.0-dev.13", path = "crates/build-utils" } revive-build-utils = { version = "0.1.0-dev.14", path = "crates/build-utils" }
hex = "0.4.3" hex = "0.4.3"
cc = "1.2" cc = "1.2"
+6 -5
View File
@@ -5,6 +5,7 @@
install-wasm \ install-wasm \
install-llvm-builder \ install-llvm-builder \
install-llvm \ install-llvm \
install-revive-runner \
format \ format \
clippy \ clippy \
machete \ machete \
@@ -39,6 +40,9 @@ install-llvm: install-llvm-builder
revive-llvm clone revive-llvm clone
revive-llvm build --llvm-projects lld --llvm-projects clang revive-llvm build --llvm-projects lld --llvm-projects clang
install-revive-runner:
cargo install --path crates/runner --no-default-features --locked
format: format:
cargo fmt --all --check cargo fmt --all --check
@@ -49,7 +53,7 @@ machete:
cargo install cargo-machete cargo install cargo-machete
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 test-integration: install-bin
cargo test --package revive-integration cargo test --package revive-integration
@@ -88,7 +92,4 @@ clean:
rm -rf node_modules ; \ rm -rf node_modules ; \
rm -rf crates/solidity/src/tests/cli-tests/artifacts ; \ rm -rf crates/solidity/src/tests/cli-tests/artifacts ; \
cargo uninstall revive-solidity ; \ cargo uninstall revive-solidity ; \
cargo uninstall revive-llvm-builder ; \ cargo uninstall revive-llvm-builder ;
rm -f package-lock.json ; \
rm -rf js/dist ; \
rm -f js/src/resolc.{wasm,js}
+8 -8
View File
@@ -1,10 +1,10 @@
{ {
"Baseline": 950, "Baseline": 939,
"Computation": 2262, "Computation": 2282,
"DivisionArithmetics": 8915, "DivisionArithmetics": 8849,
"ERC20": 17233, "ERC20": 18308,
"Events": 1628, "Events": 1640,
"FibonacciIterative": 1485, "FibonacciIterative": 1497,
"Flipper": 2132, "Flipper": 2099,
"SHA1": 8381 "SHA1": 8243
} }
+2 -8
View File
@@ -117,13 +117,7 @@ pub fn build(
log::info!("building compiler-rt for rv64emac"); log::info!("building compiler-rt for rv64emac");
crate::utils::check_presence("cmake")?; crate::utils::check_presence("cmake")?;
crate::utils::check_presence("ninja")?;
let generator = if cfg!(target_os = "windows") {
"Visual Studio 17 2022"
} else {
crate::utils::check_presence("ninja")?;
"Ninja"
};
let llvm_module_compiler_rt = crate::LLVMPath::llvm_module_compiler_rt()?; let llvm_module_compiler_rt = crate::LLVMPath::llvm_module_compiler_rt()?;
let llvm_compiler_rt_build = crate::LLVMPath::llvm_build_compiler_rt()?; let llvm_compiler_rt_build = crate::LLVMPath::llvm_build_compiler_rt()?;
@@ -136,7 +130,7 @@ pub fn build(
"-B", "-B",
llvm_compiler_rt_build.to_string_lossy().as_ref(), llvm_compiler_rt_build.to_string_lossy().as_ref(),
"-G", "-G",
generator, "Ninja",
]) ])
.args(CMAKE_STATIC_ARGS) .args(CMAKE_STATIC_ARGS)
.args(cmake_dynamic_args(build_type, target_env)?) .args(cmake_dynamic_args(build_type, target_env)?)
+3 -1
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::ir_type::IRType as DebugConfigIR;
pub use self::debug_config::DebugConfig; 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::size_level::SizeLevel as OptimizerSettingsSizeLevel;
pub use self::optimizer::settings::Settings as OptimizerSettings; pub use self::optimizer::settings::Settings as OptimizerSettings;
pub use self::optimizer::Optimizer; 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::Exit as PolkaVMExitFunction;
pub use self::polkavm::context::function::runtime::revive::WordToPointer as PolkaVMWordToPointerFunction; 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::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_DEPLOY_CODE as PolkaVMFunctionDeployCode;
pub use self::polkavm::context::function::runtime::FUNCTION_ENTRY as PolkaVMFunctionEntry; pub use self::polkavm::context::function::runtime::FUNCTION_ENTRY as PolkaVMFunctionEntry;
pub use self::polkavm::context::function::runtime::FUNCTION_RUNTIME_CODE as PolkaVMFunctionRuntimeCode; pub use self::polkavm::context::function::runtime::FUNCTION_RUNTIME_CODE as PolkaVMFunctionRuntimeCode;
@@ -60,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 as polkavm_evm_immutable;
pub use self::polkavm::evm::immutable::Load as PolkaVMLoadImmutableDataFunction; pub use self::polkavm::evm::immutable::Load as PolkaVMLoadImmutableDataFunction;
pub use self::polkavm::evm::immutable::Store as PolkaVMStoreImmutableDataFunction; pub use self::polkavm::evm::immutable::Store as PolkaVMStoreImmutableDataFunction;
pub use self::polkavm::evm::math as polkavm_evm_math; 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::memory as polkavm_evm_memory;
pub use self::polkavm::evm::r#return as polkavm_evm_return; pub use self::polkavm::evm::r#return as polkavm_evm_return;
@@ -75,6 +76,7 @@ pub use self::target_machine::target::Target;
pub use self::target_machine::TargetMachine; pub use self::target_machine::TargetMachine;
pub(crate) mod debug_config; pub(crate) mod debug_config;
pub(crate) mod memory;
pub(crate) mod optimizer; pub(crate) mod optimizer;
pub(crate) mod polkavm; pub(crate) mod polkavm;
pub(crate) mod target_machine; 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,
}
}
}
@@ -234,7 +234,7 @@ impl TryFrom<&SolcStandardJsonInputSettingsOptimizer> for Settings {
fn try_from(value: &SolcStandardJsonInputSettingsOptimizer) -> Result<Self, Self::Error> { fn try_from(value: &SolcStandardJsonInputSettingsOptimizer) -> Result<Self, Self::Error> {
let mut result = match value.mode { let mut result = match value.mode {
Some(mode) => Self::try_from_cli(mode)?, Some(mode) => Self::try_from_cli(mode)?,
None => Self::cycles(), None => Self::size(),
}; };
if value.fallback_to_optimizing_for_size.unwrap_or_default() { if value.fallback_to_optimizing_for_size.unwrap_or_default() {
result.enable_fallback_to_size(); result.enable_fallback_to_size();
@@ -9,6 +9,12 @@ pub static XLEN: usize = revive_common::BIT_LENGTH_X32;
/// The calldata size global variable name. /// The calldata size global variable name.
pub static GLOBAL_CALLDATA_SIZE: &str = "calldatasize"; 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. /// The spill buffer global variable name.
pub static GLOBAL_ADDRESS_SPILL_BUFFER: &str = "address_spill_buffer"; pub static GLOBAL_ADDRESS_SPILL_BUFFER: &str = "address_spill_buffer";
@@ -31,6 +31,23 @@ impl Entry {
context.xlen_type().get_undef(), 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); let address_type = context.integer_type(revive_common::BIT_LENGTH_ETH_ADDRESS);
context.set_global( context.set_global(
crate::polkavm::GLOBAL_ADDRESS_SPILL_BUFFER, crate::polkavm::GLOBAL_ADDRESS_SPILL_BUFFER,
@@ -5,6 +5,7 @@ pub mod deploy_code;
pub mod entry; pub mod entry;
pub mod revive; pub mod revive;
pub mod runtime_code; pub mod runtime_code;
pub mod sbrk;
/// The main entry function name. /// The main entry function name.
pub const FUNCTION_ENTRY: &str = "__entry"; 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)
}
}
+55 -71
View File
@@ -26,6 +26,7 @@ use inkwell::debug_info::DIScope;
use inkwell::types::BasicType; use inkwell::types::BasicType;
use inkwell::values::BasicValue; use inkwell::values::BasicValue;
use crate::memory::MemoryConfig;
use crate::optimizer::settings::Settings as OptimizerSettings; use crate::optimizer::settings::Settings as OptimizerSettings;
use crate::optimizer::Optimizer; use crate::optimizer::Optimizer;
use crate::polkavm::DebugConfig; use crate::polkavm::DebugConfig;
@@ -33,6 +34,7 @@ use crate::polkavm::Dependency;
use crate::target_machine::target::Target; use crate::target_machine::target::Target;
use crate::target_machine::TargetMachine; use crate::target_machine::TargetMachine;
use crate::PolkaVMLoadHeapWordFunction; use crate::PolkaVMLoadHeapWordFunction;
use crate::PolkaVMSbrkFunction;
use crate::PolkaVMStoreHeapWordFunction; use crate::PolkaVMStoreHeapWordFunction;
use self::address_space::AddressSpace; use self::address_space::AddressSpace;
@@ -85,6 +87,8 @@ where
loop_stack: Vec<Loop<'ctx>>, loop_stack: Vec<Loop<'ctx>>,
/// The extra LLVM arguments that were used during target initialization. /// The extra LLVM arguments that were used during target initialization.
llvm_arguments: &'ctx [String], llvm_arguments: &'ctx [String],
/// The PVM memory configuration.
memory_config: MemoryConfig,
/// The project dependency manager. It can be any entity implementing the trait. /// The project dependency manager. It can be any entity implementing the trait.
/// The manager is used to get information about contracts and their dependencies during /// The manager is used to get information about contracts and their dependencies during
@@ -116,9 +120,6 @@ where
/// The loop stack default capacity. /// The loop stack default capacity.
const LOOP_STACK_INITIAL_CAPACITY: usize = 16; const LOOP_STACK_INITIAL_CAPACITY: usize = 16;
/// The PolkaVM minimum stack size.
const POLKAVM_STACK_SIZE: u32 = 0x4000;
/// Link in the stdlib module. /// Link in the stdlib module.
fn link_stdlib_module( fn link_stdlib_module(
llvm: &'ctx inkwell::context::Context, llvm: &'ctx inkwell::context::Context,
@@ -218,6 +219,7 @@ where
} }
/// Initializes a new LLVM context. /// Initializes a new LLVM context.
#[allow(clippy::too_many_arguments)]
pub fn new( pub fn new(
llvm: &'ctx inkwell::context::Context, llvm: &'ctx inkwell::context::Context,
module: inkwell::module::Module<'ctx>, module: inkwell::module::Module<'ctx>,
@@ -226,11 +228,12 @@ where
include_metadata_hash: bool, include_metadata_hash: bool,
debug_config: DebugConfig, debug_config: DebugConfig,
llvm_arguments: &'ctx [String], llvm_arguments: &'ctx [String],
memory_config: MemoryConfig,
) -> Self { ) -> Self {
Self::set_data_layout(llvm, &module); Self::set_data_layout(llvm, &module);
Self::link_stdlib_module(llvm, &module); Self::link_stdlib_module(llvm, &module);
Self::link_polkavm_imports(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); Self::set_module_flags(llvm, &module);
let intrinsics = Intrinsics::new(llvm, &module); let intrinsics = Intrinsics::new(llvm, &module);
@@ -254,6 +257,7 @@ where
current_function: None, current_function: None,
loop_stack: Vec::with_capacity(Self::LOOP_STACK_INITIAL_CAPACITY), loop_stack: Vec::with_capacity(Self::LOOP_STACK_INITIAL_CAPACITY),
llvm_arguments, llvm_arguments,
memory_config,
dependency_manager, dependency_manager,
include_metadata_hash, include_metadata_hash,
@@ -644,6 +648,7 @@ where
self.include_metadata_hash, self.include_metadata_hash,
self.debug_config.clone(), self.debug_config.clone(),
self.llvm_arguments, self.llvm_arguments,
self.memory_config,
) )
}) })
} }
@@ -773,49 +778,27 @@ where
.into()) .into())
} }
/// Builds a stack load instruction with a direct assignment. /// Builds a stack load instruction.
/// Sets the alignment to 256 bits for the stack and 1 bit for the heap, parent, and child. /// Sets the alignment to 256 bits for the stack and 1 bit for the heap, parent, and child.
pub fn build_load_assign( pub fn build_load(
&self, &self,
pointer: Pointer<'ctx>, pointer: Pointer<'ctx>,
name: &str, name: &str,
assignment_pointer: &mut Option<inkwell::values::PointerValue<'ctx>>, ) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>> {
) -> anyhow::Result<Option<inkwell::values::BasicValueEnum<'ctx>>> {
match pointer.address_space { match pointer.address_space {
AddressSpace::Heap => { AddressSpace::Heap => {
let name = <PolkaVMLoadHeapWordFunction as RuntimeFunction<D>>::NAME;
let declaration = let declaration =
<PolkaVMLoadHeapWordFunction as RuntimeFunction<D>>::declaration(self); <PolkaVMLoadHeapWordFunction as RuntimeFunction<D>>::declaration(self);
match assignment_pointer.take() { let arguments = [self
Some(assignment_pointer) => { .builder()
let arguments = [ .build_ptr_to_int(pointer.value, self.xlen_type(), "offset_ptrtoint")?
self.builder() .as_basic_value_enum()];
.build_ptr_to_int( Ok(self
pointer.value, .build_call(declaration, &arguments, "heap_load")
self.xlen_type(), .unwrap_or_else(|| {
"offset_ptrtoint", panic!("revive runtime function {name} should return a value")
)? }))
.as_basic_value_enum(),
assignment_pointer.into(),
];
self.build_call(declaration, &arguments, "heap_load");
Ok(None)
}
None => {
let pointer = self.build_alloca_at_entry(self.word_type(), "pointer");
let arguments = [
self.builder()
.build_ptr_to_int(
pointer.value,
self.xlen_type(),
"offset_ptrtoint",
)?
.as_basic_value_enum(),
pointer.value.into(),
];
self.build_call(declaration, &arguments, "heap_load");
Ok(Some(self.build_load(pointer, "storage_value")?))
}
}
} }
AddressSpace::Stack => { AddressSpace::Stack => {
let value = self let value = self
@@ -828,23 +811,11 @@ where
.set_alignment(revive_common::BYTE_LENGTH_STACK_ALIGN as u32) .set_alignment(revive_common::BYTE_LENGTH_STACK_ALIGN as u32)
.expect("Alignment is valid"); .expect("Alignment is valid");
Ok(Some(value)) Ok(value)
} }
} }
} }
/// Builds a stack load instruction.
/// Sets the alignment to 256 bits for the stack and 1 bit for the heap, parent, and child.
pub fn build_load(
&self,
pointer: Pointer<'ctx>,
name: &str,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>> {
Ok(self
.build_load_assign(pointer, name, &mut None)?
.expect("without an assignment pointer loads return a value"))
}
/// Builds a stack store instruction. /// Builds a stack store instruction.
/// Sets the alignment to 256 bits for the stack and 1 bit for the heap, parent, and child. /// Sets the alignment to 256 bits for the stack and 1 bit for the heap, parent, and child.
pub fn build_store<V>(&self, pointer: Pointer<'ctx>, value: V) -> anyhow::Result<()> pub fn build_store<V>(&self, pointer: Pointer<'ctx>, value: V) -> anyhow::Result<()>
@@ -1118,32 +1089,40 @@ where
offset: inkwell::values::IntValue<'ctx>, offset: inkwell::values::IntValue<'ctx>,
size: inkwell::values::IntValue<'ctx>, size: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::PointerValue<'ctx>> { ) -> anyhow::Result<inkwell::values::PointerValue<'ctx>> {
Ok(self let call_site_value = self.builder().build_call(
.builder() <PolkaVMSbrkFunction as RuntimeFunction<D>>::declaration(self).function_value(),
.build_call( &[offset.into(), size.into()],
self.runtime_api_method(revive_runtime_api::polkavm_imports::SBRK), "alloc_start",
&[offset.into(), size.into()], )?;
"call_sbrk",
)? 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() .try_as_basic_value()
.left() .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()) .into_pointer_value())
} }
/// Build a call to PolkaVM `msize` for querying the linear memory size. /// Build a call to PolkaVM `msize` for querying the linear memory size.
pub fn build_msize(&self) -> anyhow::Result<inkwell::values::IntValue<'ctx>> { pub fn build_msize(&self) -> anyhow::Result<inkwell::values::IntValue<'ctx>> {
let memory_size_pointer = self Ok(self
.module() .get_global_value(crate::polkavm::GLOBAL_HEAP_SIZE)?
.get_global(revive_runtime_api::polkavm_imports::MEMORY_SIZE) .into_int_value())
.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())
} }
/// Returns a pointer to `offset` into the heap, allocating /// Returns a pointer to `offset` into the heap, allocating
@@ -1462,4 +1441,9 @@ where
pub fn optimizer_settings(&self) -> &OptimizerSettings { pub fn optimizer_settings(&self) -> &OptimizerSettings {
self.optimizer.settings() 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)
}
} }
@@ -2,8 +2,6 @@
use inkwell::values::BasicValueEnum; use inkwell::values::BasicValueEnum;
use crate::polkavm::context::address_space::AddressSpace;
use crate::polkavm::context::pointer::Pointer;
use crate::polkavm::context::runtime::RuntimeFunction; use crate::polkavm::context::runtime::RuntimeFunction;
use crate::polkavm::context::Context; use crate::polkavm::context::Context;
use crate::polkavm::Dependency; use crate::polkavm::Dependency;
@@ -19,13 +17,9 @@ where
const NAME: &'static str = "__revive_load_heap_word"; const NAME: &'static str = "__revive_load_heap_word";
fn r#type<'ctx>(context: &Context<'ctx, D>) -> inkwell::types::FunctionType<'ctx> { fn r#type<'ctx>(context: &Context<'ctx, D>) -> inkwell::types::FunctionType<'ctx> {
context.void_type().fn_type( context
&[ .word_type()
context.xlen_type().into(), .fn_type(&[context.xlen_type().into()], false)
context.llvm().ptr_type(Default::default()).into(),
],
false,
)
} }
fn emit_body<'ctx>( fn emit_body<'ctx>(
@@ -33,7 +27,6 @@ where
context: &mut Context<'ctx, D>, context: &mut Context<'ctx, D>,
) -> anyhow::Result<Option<BasicValueEnum<'ctx>>> { ) -> anyhow::Result<Option<BasicValueEnum<'ctx>>> {
let offset = Self::paramater(context, 0).into_int_value(); let offset = Self::paramater(context, 0).into_int_value();
let assignment_pointer = Self::paramater(context, 1).into_pointer_value();
let length = context let length = context
.xlen_type() .xlen_type()
.const_int(revive_common::BYTE_LENGTH_WORD as u64, false); .const_int(revive_common::BYTE_LENGTH_WORD as u64, false);
@@ -49,11 +42,7 @@ where
.expect("Alignment is valid"); .expect("Alignment is valid");
let swapped_value = context.build_byte_swap(value)?; let swapped_value = context.build_byte_swap(value)?;
context.build_store( Ok(Some(swapped_value))
Pointer::new(context.word_type(), AddressSpace::Stack, assignment_pointer),
swapped_value,
)?;
Ok(None)
} }
} }
@@ -2,8 +2,6 @@
use inkwell::values::BasicValueEnum; use inkwell::values::BasicValueEnum;
use crate::polkavm::context::address_space::AddressSpace;
use crate::polkavm::context::pointer::Pointer;
use crate::polkavm::context::runtime::RuntimeFunction; use crate::polkavm::context::runtime::RuntimeFunction;
use crate::polkavm::context::Context; use crate::polkavm::context::Context;
use crate::polkavm::Dependency; use crate::polkavm::Dependency;
@@ -19,27 +17,20 @@ where
const NAME: &'static str = "__revive_load_storage_word"; const NAME: &'static str = "__revive_load_storage_word";
fn r#type<'ctx>(context: &Context<'ctx, D>) -> inkwell::types::FunctionType<'ctx> { fn r#type<'ctx>(context: &Context<'ctx, D>) -> inkwell::types::FunctionType<'ctx> {
context.void_type().fn_type( context
&[ .word_type()
context.llvm().ptr_type(Default::default()).into(), .fn_type(&[context.llvm().ptr_type(Default::default()).into()], false)
context.llvm().ptr_type(Default::default()).into(),
],
false,
)
} }
fn emit_body<'ctx>( fn emit_body<'ctx>(
&self, &self,
context: &mut Context<'ctx, D>, context: &mut Context<'ctx, D>,
) -> anyhow::Result<Option<BasicValueEnum<'ctx>>> { ) -> anyhow::Result<Option<BasicValueEnum<'ctx>>> {
let key = Self::paramater(context, 0); Ok(Some(emit_load(
let assignment_pointer = Self::paramater(context, 1).into_pointer_value(); context,
let value = emit_load(context, key, false)?; Self::paramater(context, 0),
context.build_store( false,
Pointer::new(context.word_type(), AddressSpace::Stack, assignment_pointer), )?))
value,
)?;
Ok(None)
} }
} }
@@ -23,6 +23,7 @@ pub fn create_context(
true, true,
Default::default(), Default::default(),
Default::default(), Default::default(),
Default::default(),
) )
} }
@@ -29,8 +29,7 @@ where
pub fn load<'ctx, D>( pub fn load<'ctx, D>(
context: &mut Context<'ctx, D>, context: &mut Context<'ctx, D>,
offset: inkwell::values::IntValue<'ctx>, offset: inkwell::values::IntValue<'ctx>,
assignment_pointer: &mut Option<inkwell::values::PointerValue<'ctx>>, ) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
) -> anyhow::Result<Option<inkwell::values::BasicValueEnum<'ctx>>>
where where
D: Dependency + Clone, D: Dependency + Clone,
{ {
@@ -41,7 +40,7 @@ where
offset, offset,
"memory_load_pointer", "memory_load_pointer",
); );
context.build_load_assign(pointer, "memory_load_result", assignment_pointer) context.build_load(pointer, "memory_load_result")
} }
/// Translates the `mstore` instruction. /// Translates the `mstore` instruction.
+6 -22
View File
@@ -13,32 +13,16 @@ use crate::PolkaVMStoreTransientStorageWordFunction;
pub fn load<'ctx, D>( pub fn load<'ctx, D>(
context: &mut Context<'ctx, D>, context: &mut Context<'ctx, D>,
position: &PolkaVMArgument<'ctx>, position: &PolkaVMArgument<'ctx>,
assignment_pointer: &mut Option<inkwell::values::PointerValue<'ctx>>, ) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
) -> anyhow::Result<Option<inkwell::values::BasicValueEnum<'ctx>>>
where where
D: Dependency + Clone, D: Dependency + Clone,
{ {
let _name = <PolkaVMLoadStorageWordFunction as RuntimeFunction<D>>::NAME; let name = <PolkaVMLoadStorageWordFunction as RuntimeFunction<D>>::NAME;
let declaration = <PolkaVMLoadStorageWordFunction as RuntimeFunction<D>>::declaration(context); let declaration = <PolkaVMLoadStorageWordFunction as RuntimeFunction<D>>::declaration(context);
match assignment_pointer.take() { let arguments = [position.as_pointer(context)?.value.into()];
Some(assignment_pointer) => { Ok(context
let arguments = [ .build_call(declaration, &arguments, "storage_load")
position.as_pointer(context)?.value.into(), .unwrap_or_else(|| panic!("runtime function {name} should return a value")))
assignment_pointer.into(),
];
context.build_call(declaration, &arguments, "storage_load");
Ok(None)
}
None => {
let pointer = context.build_alloca_at_entry(context.word_type(), "pointer");
let arguments = [
position.as_pointer(context)?.value.into(),
pointer.value.into(),
];
context.build_call(declaration, &arguments, "storage_load");
Ok(Some(context.build_load(pointer, "storage_value")?))
}
}
} }
/// Translates the storage store. /// Translates the storage store.
+3
View File
@@ -7,6 +7,7 @@ pub mod evm;
pub use self::r#const::*; pub use self::r#const::*;
use crate::debug_config::DebugConfig; use crate::debug_config::DebugConfig;
use crate::memory::MemoryConfig;
use crate::optimizer::settings::Settings as OptimizerSettings; use crate::optimizer::settings::Settings as OptimizerSettings;
use anyhow::Context as AnyhowContext; use anyhow::Context as AnyhowContext;
@@ -90,6 +91,7 @@ pub trait Dependency {
include_metadata_hash: bool, include_metadata_hash: bool,
debug_config: DebugConfig, debug_config: DebugConfig,
llvm_arguments: &[String], llvm_arguments: &[String],
memory_config: MemoryConfig,
) -> anyhow::Result<String>; ) -> anyhow::Result<String>;
/// Resolves a full contract path. /// Resolves a full contract path.
@@ -111,6 +113,7 @@ impl Dependency for DummyDependency {
_include_metadata_hash: bool, _include_metadata_hash: bool,
_debug_config: DebugConfig, _debug_config: DebugConfig,
_llvm_arguments: &[String], _llvm_arguments: &[String],
_memory_config: MemoryConfig,
) -> anyhow::Result<String> { ) -> anyhow::Result<String> {
Ok(String::new()) Ok(String::new())
} }
+9 -2
View File
@@ -10,12 +10,19 @@ description = "Execute revive contracts in a simulated blockchain runtime"
[package.metadata.cargo-machete] [package.metadata.cargo-machete]
ignored = ["codec", "scale-info"] ignored = ["codec", "scale-info"]
[[bin]]
name = "revive-runner"
path = "src/main.rs"
[features] [features]
std = ["polkadot-sdk/std"] std = ["polkadot-sdk/std"]
default = ["solidity"] default = ["solidity"]
solidity = ["revive-solidity", "revive-differential"] solidity = ["revive-solidity", "revive-differential", "revive-llvm-context"]
[dependencies] [dependencies]
env_logger = { workspace = true }
clap = { workspace = true, features = ["help", "std", "derive"] }
anyhow = { workspace = true }
serde = { workspace = true } serde = { workspace = true }
serde_json = { workspace = true } serde_json = { workspace = true }
hex = { workspace = true, features = ["serde"] } hex = { workspace = true, features = ["serde"] }
@@ -34,4 +41,4 @@ polkadot-sdk.features = [
revive-solidity = { workspace = true, optional = true } revive-solidity = { workspace = true, optional = true }
revive-differential = { 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 serde::{Deserialize, Serialize};
use crate::*; use crate::*;
use alloy_primitives::{keccak256, Address}; use alloy_primitives::keccak256;
#[cfg(feature = "revive-solidity")]
use alloy_primitives::Address;
#[cfg(feature = "revive-solidity")] #[cfg(feature = "revive-solidity")]
use revive_differential::{Evm, EvmLog}; use revive_differential::{Evm, EvmLog};
#[cfg(feature = "revive-solidity")] #[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 /// Specs for a contract test
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(default)] #[serde(default)]
@@ -212,6 +247,7 @@ impl Specs {
/// Helper to allow not specifying the code bytes or path directly in the runner.json /// 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::Bytes(bytes)` if `bytes` are empty: read `contract_file`
/// - Replace `Code::Solidity{ path, ..}` if `path` is not provided: replace `path` with `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) { pub fn replace_empty_code(&mut self, contract_name: &str, contract_path: &str) {
for action in self.actions.iter_mut() { for action in self.actions.iter_mut() {
let code = match action { let code = match action {
-28
View File
@@ -5,34 +5,6 @@
// Missing builtins // 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) { void * memcpy(void *dst, const void *_src, size_t len) {
uint8_t *dest = dst; uint8_t *dest = dst;
const uint8_t *src = _src; 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")); 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 ADDRESS: &str = "address";
pub static BALANCE: &str = "balance"; pub static BALANCE: &str = "balance";
@@ -78,8 +70,7 @@ pub static WEIGHT_TO_FEE: &str = "weight_to_fee";
/// All imported runtime API symbols. /// All imported runtime API symbols.
/// Useful for configuring common attributes and linkage. /// Useful for configuring common attributes and linkage.
pub static IMPORTS: [&str; 34] = [ pub static IMPORTS: [&str; 33] = [
SBRK,
ADDRESS, ADDRESS,
BALANCE, BALANCE,
BALANCE_OF, BALANCE_OF,
+11
View File
@@ -55,6 +55,7 @@ pub fn yul<T: Compiler>(
include_metadata_hash: bool, include_metadata_hash: bool,
debug_config: revive_llvm_context::DebugConfig, debug_config: revive_llvm_context::DebugConfig,
llvm_arguments: &[String], llvm_arguments: &[String],
memory_config: revive_llvm_context::MemoryConfig,
) -> anyhow::Result<Build> { ) -> anyhow::Result<Build> {
let path = match input_files.len() { let path = match input_files.len() {
1 => input_files.first().expect("Always exists"), 1 => input_files.first().expect("Always exists"),
@@ -80,6 +81,7 @@ pub fn yul<T: Compiler>(
include_metadata_hash, include_metadata_hash,
debug_config, debug_config,
llvm_arguments, llvm_arguments,
memory_config,
)?; )?;
Ok(build) Ok(build)
@@ -92,6 +94,7 @@ pub fn llvm_ir(
include_metadata_hash: bool, include_metadata_hash: bool,
debug_config: revive_llvm_context::DebugConfig, debug_config: revive_llvm_context::DebugConfig,
llvm_arguments: &[String], llvm_arguments: &[String],
memory_config: revive_llvm_context::MemoryConfig,
) -> anyhow::Result<Build> { ) -> anyhow::Result<Build> {
let path = match input_files.len() { let path = match input_files.len() {
1 => input_files.first().expect("Always exists"), 1 => input_files.first().expect("Always exists"),
@@ -109,6 +112,7 @@ pub fn llvm_ir(
include_metadata_hash, include_metadata_hash,
debug_config, debug_config,
llvm_arguments, llvm_arguments,
memory_config,
)?; )?;
Ok(build) Ok(build)
@@ -131,6 +135,7 @@ pub fn standard_output<T: Compiler>(
suppressed_warnings: Option<Vec<ResolcWarning>>, suppressed_warnings: Option<Vec<ResolcWarning>>,
debug_config: revive_llvm_context::DebugConfig, debug_config: revive_llvm_context::DebugConfig,
llvm_arguments: &[String], llvm_arguments: &[String],
memory_config: revive_llvm_context::MemoryConfig,
) -> anyhow::Result<Build> { ) -> anyhow::Result<Build> {
let solc_version = solc.version()?; let solc_version = solc.version()?;
@@ -189,12 +194,14 @@ pub fn standard_output<T: Compiler>(
include_metadata_hash, include_metadata_hash,
debug_config, debug_config,
llvm_arguments, llvm_arguments,
memory_config,
)?; )?;
Ok(build) Ok(build)
} }
/// Runs the standard JSON mode. /// Runs the standard JSON mode.
#[allow(clippy::too_many_arguments)]
pub fn standard_json<T: Compiler>( pub fn standard_json<T: Compiler>(
solc: &mut T, solc: &mut T,
detect_missing_libraries: bool, detect_missing_libraries: bool,
@@ -203,6 +210,7 @@ pub fn standard_json<T: Compiler>(
allow_paths: Option<String>, allow_paths: Option<String>,
debug_config: revive_llvm_context::DebugConfig, debug_config: revive_llvm_context::DebugConfig,
llvm_arguments: &[String], llvm_arguments: &[String],
memory_config: revive_llvm_context::MemoryConfig,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
let solc_version = solc.version()?; let solc_version = solc.version()?;
@@ -250,6 +258,7 @@ pub fn standard_json<T: Compiler>(
include_metadata_hash, include_metadata_hash,
debug_config, debug_config,
llvm_arguments, llvm_arguments,
memory_config,
)?; )?;
build.write_to_standard_json(&mut solc_output, &solc_version)?; build.write_to_standard_json(&mut solc_output, &solc_version)?;
} }
@@ -277,6 +286,7 @@ pub fn combined_json<T: Compiler>(
output_directory: Option<PathBuf>, output_directory: Option<PathBuf>,
overwrite: bool, overwrite: bool,
llvm_arguments: &[String], llvm_arguments: &[String],
memory_config: revive_llvm_context::MemoryConfig,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
let build = standard_output( let build = standard_output(
input_files, input_files,
@@ -293,6 +303,7 @@ pub fn combined_json<T: Compiler>(
suppressed_warnings, suppressed_warnings,
debug_config, debug_config,
llvm_arguments, llvm_arguments,
memory_config,
)?; )?;
let mut combined_json = solc.combined_json(input_files, format.as_str())?; 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, pub debug_config: revive_llvm_context::DebugConfig,
/// The extra LLVM arguments give used for manual control. /// The extra LLVM arguments give used for manual control.
pub llvm_arguments: Vec<String>, pub llvm_arguments: Vec<String>,
/// The PVM memory configuration.
pub memory_config: revive_llvm_context::MemoryConfig,
} }
impl Input { impl Input {
@@ -33,6 +35,7 @@ impl Input {
optimizer_settings: revive_llvm_context::OptimizerSettings, optimizer_settings: revive_llvm_context::OptimizerSettings,
debug_config: revive_llvm_context::DebugConfig, debug_config: revive_llvm_context::DebugConfig,
llvm_arguments: Vec<String>, llvm_arguments: Vec<String>,
memory_config: revive_llvm_context::MemoryConfig,
) -> Self { ) -> Self {
Self { Self {
contract, contract,
@@ -41,6 +44,7 @@ impl Input {
optimizer_settings, optimizer_settings,
debug_config, debug_config,
llvm_arguments, llvm_arguments,
memory_config,
} }
} }
} }
+1
View File
@@ -50,6 +50,7 @@ pub trait Process {
input.include_metadata_hash, input.include_metadata_hash,
input.debug_config, input.debug_config,
&input.llvm_arguments, &input.llvm_arguments,
input.memory_config,
); );
match result { match result {
@@ -78,6 +78,7 @@ impl Contract {
include_metadata_hash: bool, include_metadata_hash: bool,
debug_config: revive_llvm_context::DebugConfig, debug_config: revive_llvm_context::DebugConfig,
llvm_arguments: &[String], llvm_arguments: &[String],
memory_config: revive_llvm_context::MemoryConfig,
) -> anyhow::Result<ContractBuild> { ) -> anyhow::Result<ContractBuild> {
let llvm = inkwell::context::Context::create(); let llvm = inkwell::context::Context::create();
let optimizer = revive_llvm_context::Optimizer::new(optimizer_settings); let optimizer = revive_llvm_context::Optimizer::new(optimizer_settings);
@@ -123,6 +124,7 @@ impl Contract {
include_metadata_hash, include_metadata_hash,
debug_config, debug_config,
llvm_arguments, llvm_arguments,
memory_config,
); );
context.set_solidity_data(revive_llvm_context::PolkaVMContextSolidityData::default()); context.set_solidity_data(revive_llvm_context::PolkaVMContextSolidityData::default());
match self.ir { match self.ir {
+4
View File
@@ -67,6 +67,7 @@ impl Project {
include_metadata_hash: bool, include_metadata_hash: bool,
debug_config: revive_llvm_context::DebugConfig, debug_config: revive_llvm_context::DebugConfig,
llvm_arguments: &[String], llvm_arguments: &[String],
memory_config: revive_llvm_context::MemoryConfig,
) -> anyhow::Result<Build> { ) -> anyhow::Result<Build> {
let project = self.clone(); let project = self.clone();
#[cfg(feature = "parallel")] #[cfg(feature = "parallel")]
@@ -83,6 +84,7 @@ impl Project {
optimizer_settings.clone(), optimizer_settings.clone(),
debug_config.clone(), debug_config.clone(),
llvm_arguments.to_vec(), llvm_arguments.to_vec(),
memory_config,
); );
let process_output = { let process_output = {
#[cfg(target_os = "emscripten")] #[cfg(target_os = "emscripten")]
@@ -319,6 +321,7 @@ impl revive_llvm_context::PolkaVMDependency for Project {
include_metadata_hash: bool, include_metadata_hash: bool,
debug_config: revive_llvm_context::DebugConfig, debug_config: revive_llvm_context::DebugConfig,
llvm_arguments: &[String], llvm_arguments: &[String],
memory_config: revive_llvm_context::MemoryConfig,
) -> anyhow::Result<String> { ) -> anyhow::Result<String> {
let contract_path = project.resolve_path(identifier)?; let contract_path = project.resolve_path(identifier)?;
let contract = project let contract = project
@@ -339,6 +342,7 @@ impl revive_llvm_context::PolkaVMDependency for Project {
include_metadata_hash, include_metadata_hash,
debug_config, debug_config,
llvm_arguments, llvm_arguments,
memory_config,
) )
.map_err(|error| { .map_err(|error| {
anyhow::anyhow!( anyhow::anyhow!(
+33 -1
View File
@@ -167,9 +167,41 @@ pub struct Arguments {
#[arg(long = "recursive-process-input")] #[arg(long = "recursive-process-input")]
pub recursive_process_input: Option<String>, pub recursive_process_input: Option<String>,
#[arg(long = "llvm-arg")]
/// These are passed to LLVM as the command line to allow manual control. /// These are passed to LLVM as the command line to allow manual control.
#[arg(long = "llvm-arg")]
pub llvm_arguments: Vec<String>, 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 { impl Arguments {
+11 -1
View File
@@ -129,7 +129,7 @@ fn main_inner() -> anyhow::Result<()> {
let mut optimizer_settings = match arguments.optimization { let mut optimizer_settings = match arguments.optimization {
Some(mode) => revive_llvm_context::OptimizerSettings::try_from_cli(mode)?, Some(mode) => revive_llvm_context::OptimizerSettings::try_from_cli(mode)?,
None => revive_llvm_context::OptimizerSettings::cycles(), None => revive_llvm_context::OptimizerSettings::size(),
}; };
if arguments.fallback_to_optimizing_for_size { if arguments.fallback_to_optimizing_for_size {
optimizer_settings.enable_fallback_to_size(); optimizer_settings.enable_fallback_to_size();
@@ -148,6 +148,11 @@ fn main_inner() -> anyhow::Result<()> {
None => true, None => true,
}; };
let memory_config = revive_llvm_context::MemoryConfig {
heap_size: arguments.heap_size,
stack_size: arguments.stack_size,
};
let build = if arguments.yul { let build = if arguments.yul {
revive_solidity::yul( revive_solidity::yul(
input_files.as_slice(), input_files.as_slice(),
@@ -156,6 +161,7 @@ fn main_inner() -> anyhow::Result<()> {
include_metadata_hash, include_metadata_hash,
debug_config, debug_config,
&arguments.llvm_arguments, &arguments.llvm_arguments,
memory_config,
) )
} else if arguments.llvm_ir { } else if arguments.llvm_ir {
revive_solidity::llvm_ir( revive_solidity::llvm_ir(
@@ -164,6 +170,7 @@ fn main_inner() -> anyhow::Result<()> {
include_metadata_hash, include_metadata_hash,
debug_config, debug_config,
&arguments.llvm_arguments, &arguments.llvm_arguments,
memory_config,
) )
} else if arguments.standard_json { } else if arguments.standard_json {
revive_solidity::standard_json( revive_solidity::standard_json(
@@ -174,6 +181,7 @@ fn main_inner() -> anyhow::Result<()> {
arguments.allow_paths, arguments.allow_paths,
debug_config, debug_config,
&arguments.llvm_arguments, &arguments.llvm_arguments,
memory_config,
)?; )?;
return Ok(()); return Ok(());
} else if let Some(format) = arguments.combined_json { } else if let Some(format) = arguments.combined_json {
@@ -195,6 +203,7 @@ fn main_inner() -> anyhow::Result<()> {
arguments.output_directory, arguments.output_directory,
arguments.overwrite, arguments.overwrite,
&arguments.llvm_arguments, &arguments.llvm_arguments,
memory_config,
)?; )?;
return Ok(()); return Ok(());
} else { } else {
@@ -213,6 +222,7 @@ fn main_inner() -> anyhow::Result<()> {
suppressed_warnings, suppressed_warnings,
debug_config, debug_config,
&arguments.llvm_arguments, &arguments.llvm_arguments,
memory_config,
) )
}?; }?;
+14 -3
View File
@@ -115,8 +115,13 @@ pub fn build_solidity_with_options(
&debug_config, &debug_config,
)?; )?;
let build: crate::Build = let build: crate::Build = project.compile(
project.compile(optimizer_settings, false, debug_config, Default::default())?; optimizer_settings,
false,
debug_config,
Default::default(),
Default::default(),
)?;
build.write_to_standard_json(&mut output, &solc_version)?; build.write_to_standard_json(&mut output, &solc_version)?;
Ok(output) Ok(output)
@@ -243,7 +248,13 @@ pub fn build_yul(source_code: &str) -> anyhow::Result<()> {
source_code, source_code,
None, 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(()) Ok(())
} }
@@ -1,19 +1,33 @@
import * as path from 'path'; import * as path from 'path'
const outputDir = 'artifacts'; const outputDir = 'artifacts'
const binExtension = ':C.pvm'; const binExtension = ':C.pvm'
const asmExtension = ':C.pvmasm'; const asmExtension = ':C.pvmasm'
const llvmExtension = '.ll'; const llvmExtension = '.ll'
const contractSolFilename = 'contract.sol'; const contractSolFilename = 'contract.sol'
const contractYulFilename = 'contract.yul'; const contractYulFilename = 'contract.yul'
const contractOptimizedLLVMFilename = contractSolFilename + '.C.optimized'; const contractOptimizedLLVMFilename = contractSolFilename + '.C.optimized'
const contractUnoptimizedLLVMFilename = contractSolFilename + '.C.unoptimized'; const contractUnoptimizedLLVMFilename = contractSolFilename + '.C.unoptimized'
const pathToOutputDir = path.join(__dirname, '..', outputDir); const pathToOutputDir = path.join(__dirname, '..', outputDir)
const pathToContracts = path.join(__dirname, '..', 'src', 'contracts'); const pathToContracts = path.join(__dirname, '..', 'src', 'contracts')
const pathToBasicYulContract = path.join(pathToContracts, 'yul', contractYulFilename); const pathToBasicYulContract = path.join(
const pathToBasicSolContract = path.join(pathToContracts, 'solidity', contractSolFilename); pathToContracts,
const pathToSolBinOutputFile = path.join(pathToOutputDir, contractSolFilename + binExtension); 'yul',
const pathToSolAsmOutputFile = path.join(pathToOutputDir, contractSolFilename + asmExtension); contractYulFilename
)
const pathToBasicSolContract = path.join(
pathToContracts,
'solidity',
contractSolFilename
)
const pathToSolBinOutputFile = path.join(
pathToOutputDir,
contractSolFilename + binExtension
)
const pathToSolAsmOutputFile = path.join(
pathToOutputDir,
contractSolFilename + asmExtension
)
export const paths = { export const paths = {
outputDir: outputDir, outputDir: outputDir,
@@ -30,4 +44,4 @@ export const paths = {
pathToBasicYulContract: pathToBasicYulContract, pathToBasicYulContract: pathToBasicYulContract,
pathToSolBinOutputFile: pathToSolBinOutputFile, pathToSolBinOutputFile: pathToSolBinOutputFile,
pathToSolAsmOutputFile: pathToSolAsmOutputFile, pathToSolAsmOutputFile: pathToSolAsmOutputFile,
}; }
@@ -1,44 +1,51 @@
import * as shell from 'shelljs'; import * as shell from 'shelljs'
import * as fs from 'fs'; import * as fs from 'fs'
import { spawnSync } from 'child_process'; import { spawnSync } from 'child_process'
interface CommandResult { interface CommandResult {
output: string; output: string
exitCode: number; exitCode: number
} }
export const executeCommand = (command: string, stdin?: string): CommandResult => { export const executeCommand = (
if (stdin) { command: string,
const process = spawnSync(command, [], { stdin?: string
input: stdin, ): CommandResult => {
shell: true, if (stdin) {
encoding: 'utf8', const process = spawnSync(command, [], {
maxBuffer: 30 * 1024 * 1024 input: stdin,
}); shell: true,
encoding: 'utf8',
maxBuffer: 30 * 1024 * 1024,
})
return {
exitCode: process.status || 0,
output: (process.stdout || process.stderr || '').toString()
};
}
const result = shell.exec(command, { silent: true, async: false });
return { return {
exitCode: result.code, exitCode: process.status || 0,
output: result.stdout || result.stderr || '' output: (process.stdout || process.stderr || '').toString(),
}; }
}; }
const result = shell.exec(command, { silent: true, async: false })
return {
exitCode: result.code,
output: result.stdout || result.stderr || '',
}
}
export const isFolderExist = (folder: string): boolean => { export const isFolderExist = (folder: string): boolean => {
return shell.test('-d', folder); return shell.test('-d', folder)
}; }
export const isFileExist = (pathToFileDir: string, fileName: string, fileExtension: string): boolean => { export const isFileExist = (
return shell.ls(pathToFileDir).stdout.includes(fileName + fileExtension); pathToFileDir: string,
}; fileName: string,
fileExtension: string
): boolean => {
return shell.ls(pathToFileDir).stdout.includes(fileName + fileExtension)
}
export const isFileEmpty = (file: string): boolean => { export const isFileEmpty = (file: string): boolean => {
if (fs.existsSync(file)) { if (fs.existsSync(file)) {
return (fs.readFileSync(file).length === 0); return fs.readFileSync(file).length === 0
} }
}; }
@@ -1,43 +1,44 @@
import {executeCommand} from "../src/helper"; import { executeCommand } from '../src/helper'
import { paths } from '../src/entities'; import { paths } from '../src/entities'
//id1746 //id1746
describe("Run with --asm by default", () => { describe('Run with --asm by default', () => {
const command = `resolc ${paths.pathToBasicSolContract} --asm`; const command = `resolc ${paths.pathToBasicSolContract} --asm`
const result = executeCommand(command); const result = executeCommand(command)
const commandInvalid = 'resolc --asm'; const commandInvalid = 'resolc --asm'
const resultInvalid = executeCommand(commandInvalid); const resultInvalid = executeCommand(commandInvalid)
it("Valid command exit code = 0", () => { it('Valid command exit code = 0', () => {
expect(result.exitCode).toBe(0); expect(result.exitCode).toBe(0)
}); })
it("--asm output is presented", () => { it('--asm output is presented', () => {
const expectedPatterns = [/(deploy)/i, /(call)/i, /(seal_return)/i]; const expectedPatterns = [/(deploy)/i, /(call)/i, /(seal_return)/i]
for (const pattern of expectedPatterns) { for (const pattern of expectedPatterns) {
expect(result.output).toMatch(pattern); expect(result.output).toMatch(pattern)
} }
}); })
it("solc exit code == resolc exit code", () => { it('solc exit code == resolc exit code', () => {
const command = `solc ${paths.pathToBasicSolContract} --asm`; const command = `solc ${paths.pathToBasicSolContract} --asm`
const solcResult = executeCommand(command); const solcResult = executeCommand(command)
expect(solcResult.exitCode).toBe(result.exitCode); expect(solcResult.exitCode).toBe(result.exitCode)
}); })
it("run invalid: resolc --asm", () => { it('run invalid: resolc --asm', () => {
expect(resultInvalid.output).toMatch(/(No input sources specified|Compilation aborted)/i); expect(resultInvalid.output).toMatch(
}); /(No input sources specified|Compilation aborted)/i
)
it("Invalid command exit code = 1", () => { })
expect(resultInvalid.exitCode).toBe(1);
});
it("Invalid solc exit code == Invalid resolc exit code", () => { it('Invalid command exit code = 1', () => {
const command = 'solc --asm'; expect(resultInvalid.exitCode).toBe(1)
const solcResult = executeCommand(command); })
expect(solcResult.exitCode).toBe(resultInvalid.exitCode);
}); it('Invalid solc exit code == Invalid resolc exit code', () => {
}); const command = 'solc --asm'
const solcResult = executeCommand(command)
expect(solcResult.exitCode).toBe(resultInvalid.exitCode)
})
})
@@ -1,191 +1,241 @@
import { executeCommand, isFolderExist, isFileExist, isFileEmpty } from "../src/helper"; import {
import { paths } from '../src/entities'; executeCommand,
import * as shell from 'shelljs'; isFolderExist,
import * as path from 'path'; isFileExist,
isFileEmpty,
} from '../src/helper'
import { paths } from '../src/entities'
import * as shell from 'shelljs'
import * as path from 'path'
//id1762 //id1762
describe("Run resolc without any options", () => { describe('Run resolc without any options', () => {
const command = 'resolc'; const command = 'resolc'
const result = executeCommand(command); const result = executeCommand(command)
it("Info with help is presented", () => { it('Info with help is presented', () => {
expect(result.output).toMatch(/(Usage: resolc)/i); expect(result.output).toMatch(/(Usage: resolc)/i)
}); })
it("Exit code = 1", () => { it('Exit code = 1', () => {
expect(result.exitCode).toBe(1); expect(result.exitCode).toBe(1)
}); })
it("solc exit code == resolc exit code", () => {
const command = 'solc';
const solcResult = executeCommand(command);
expect(solcResult.exitCode).toBe(result.exitCode);
});
});
it('solc exit code == resolc exit code', () => {
const command = 'solc'
const solcResult = executeCommand(command)
expect(solcResult.exitCode).toBe(result.exitCode)
})
})
//#1713 //#1713
describe("Default run a command from the help", () => { describe('Default run a command from the help', () => {
const command = `resolc ${paths.pathToBasicSolContract} --overwrite -O3 --bin --output-dir "${paths.pathToOutputDir}"` // potential issue on resolc with full path on Windows cmd
const result = executeCommand(command)
const command = `resolc ${paths.pathToBasicSolContract} --overwrite -O3 --bin --output-dir "${paths.pathToOutputDir}"`; // potential issue on resolc with full path on Windows cmd it('Compiler run successful', () => {
const result = executeCommand(command); expect(result.output).toMatch(/(Compiler run successful.)/i)
})
it("Compiler run successful", () => { it('Exit code = 0', () => {
expect(result.output).toMatch(/(Compiler run successful.)/i); expect(result.exitCode).toBe(0)
}); })
it("Exit code = 0", () => { it('Output dir is created', () => {
expect(result.exitCode).toBe(0); expect(isFolderExist(paths.pathToOutputDir)).toBe(true)
}); })
it("Output dir is created", () => { xit('Output file is created', () => {
expect(isFolderExist(paths.pathToOutputDir)).toBe(true); // a bug on windows
}); expect(
xit("Output file is created", () => { // a bug on windows isFileExist(
expect(isFileExist(paths.pathToOutputDir, paths.contractSolFilename, paths.binExtension)).toBe(true); paths.pathToOutputDir,
}); paths.contractSolFilename,
it("the output file is not empty", () => { paths.binExtension
expect(isFileEmpty(paths.pathToSolBinOutputFile)).toBe(false); )
}); ).toBe(true)
it("No 'Error'/'Warning'/'Fail' in the output", () => { })
expect(result.output).not.toMatch(/([Ee]rror|[Ww]arning|[Ff]ail)/i); it('the output file is not empty', () => {
}); expect(isFileEmpty(paths.pathToSolBinOutputFile)).toBe(false)
}); })
it("No 'Error'/'Warning'/'Fail' in the output", () => {
expect(result.output).not.toMatch(/([Ee]rror|[Ww]arning|[Ff]ail)/i)
})
})
//#1818 //#1818
describe("Default run a command from the help", () => { describe('Default run a command from the help', () => {
const command = `resolc ${paths.pathToBasicSolContract} --overwrite -O3 --bin --asm --output-dir "${paths.pathToOutputDir}"` // potential issue on resolc with full path on Windows cmd
const result = executeCommand(command)
const command = `resolc ${paths.pathToBasicSolContract} --overwrite -O3 --bin --asm --output-dir "${paths.pathToOutputDir}"`; // potential issue on resolc with full path on Windows cmd it('Compiler run successful', () => {
const result = executeCommand(command); expect(result.output).toMatch(/(Compiler run successful.)/i)
})
it('Exit code = 0', () => {
expect(result.exitCode).toBe(0)
})
it('Output dir is created', () => {
expect(isFolderExist(paths.pathToOutputDir)).toBe(true)
})
xit('Output files are created', () => {
// a bug on windows
expect(
isFileExist(
paths.pathToOutputDir,
paths.contractSolFilename,
paths.binExtension
)
).toBe(true)
expect(
isFileExist(
paths.pathToOutputDir,
paths.contractSolFilename,
paths.asmExtension
)
).toBe(true)
})
it('the output files are not empty', () => {
expect(isFileEmpty(paths.pathToSolBinOutputFile)).toBe(false)
expect(isFileEmpty(paths.pathToSolAsmOutputFile)).toBe(false)
})
it("No 'Error'/'Warning'/'Fail' in the output", () => {
expect(result.output).not.toMatch(/([Ee]rror|[Ww]arning|[Ff]ail)/i)
})
})
it("Compiler run successful", () => { describe('Run resolc with source debug information', () => {
expect(result.output).toMatch(/(Compiler run successful.)/i); const commands = [
}); `resolc -g ${paths.pathToBasicSolContract} --overwrite --bin --asm --output-dir "${paths.pathToOutputDir}"`,
it("Exit code = 0", () => { `resolc --disable-solc-optimizer -g ${paths.pathToBasicSolContract} --overwrite --bin --asm --output-dir "${paths.pathToOutputDir}"`,
expect(result.exitCode).toBe(0); ] // potential issue on resolc with full path on Windows cmd`;
});
it("Output dir is created", () => {
expect(isFolderExist(paths.pathToOutputDir)).toBe(true);
});
xit("Output files are created", () => { // a bug on windows
expect(isFileExist(paths.pathToOutputDir, paths.contractSolFilename, paths.binExtension)).toBe(true);
expect(isFileExist(paths.pathToOutputDir, paths.contractSolFilename, paths.asmExtension)).toBe(true);
});
it("the output files are not empty", () => {
expect(isFileEmpty(paths.pathToSolBinOutputFile)).toBe(false);
expect(isFileEmpty(paths.pathToSolAsmOutputFile)).toBe(false);
});
it("No 'Error'/'Warning'/'Fail' in the output", () => {
expect(result.output).not.toMatch(/([Ee]rror|[Ww]arning|[Ff]ail)/i);
});
});
describe("Run resolc with source debug information", () => { for (var idx in commands) {
const commands = [ const command = commands[idx]
`resolc -g ${paths.pathToBasicSolContract} --overwrite --bin --asm --output-dir "${paths.pathToOutputDir}"`, const result = executeCommand(command)
`resolc --disable-solc-optimizer -g ${paths.pathToBasicSolContract} --overwrite --bin --asm --output-dir "${paths.pathToOutputDir}"`
]; // potential issue on resolc with full path on Windows cmd`;
for (var idx in commands) { it('Compiler run successful', () => {
const command = commands[idx]; expect(result.output).toMatch(/(Compiler run successful.)/i)
const result = executeCommand(command); })
it('Exit code = 0', () => {
expect(result.exitCode).toBe(0)
})
it('Output dir is created', () => {
expect(isFolderExist(paths.pathToOutputDir)).toBe(true)
})
it('Output files are created', () => {
// a bug on windows
expect(
isFileExist(
paths.pathToOutputDir,
paths.contractSolFilename,
paths.binExtension
)
).toBe(true)
expect(
isFileExist(
paths.pathToOutputDir,
paths.contractSolFilename,
paths.asmExtension
)
).toBe(true)
})
it('the output files are not empty', () => {
expect(isFileEmpty(paths.pathToSolBinOutputFile)).toBe(false)
expect(isFileEmpty(paths.pathToSolAsmOutputFile)).toBe(false)
})
it("No 'Error'/'Fail' in the output", () => {
expect(result.output).not.toMatch(/([Ee]rror|[Ff]ail)/i)
})
}
})
it("Compiler run successful", () => { describe('Run resolc with source debug information, check LLVM debug-info', () => {
expect(result.output).toMatch(/(Compiler run successful.)/i); const commands = [
}); `resolc -g ${paths.pathToBasicSolContract} --overwrite --debug-output-dir="${paths.pathToOutputDir}"`,
it("Exit code = 0", () => { `resolc -g --disable-solc-optimizer ${paths.pathToBasicSolContract} --overwrite --debug-output-dir="${paths.pathToOutputDir}"`,
expect(result.exitCode).toBe(0); ] // potential issue on resolc with full path on Windows cmd`;
});
it("Output dir is created", () => {
expect(isFolderExist(paths.pathToOutputDir)).toBe(true);
});
it("Output files are created", () => { // a bug on windows
expect(isFileExist(paths.pathToOutputDir, paths.contractSolFilename, paths.binExtension)).toBe(true);
expect(isFileExist(paths.pathToOutputDir, paths.contractSolFilename, paths.asmExtension)).toBe(true);
});
it("the output files are not empty", () => {
expect(isFileEmpty(paths.pathToSolBinOutputFile)).toBe(false);
expect(isFileEmpty(paths.pathToSolAsmOutputFile)).toBe(false);
});
it("No 'Error'/'Fail' in the output", () => {
expect(result.output).not.toMatch(/([Ee]rror|[Ff]ail)/i);
});
}
});
describe("Run resolc with source debug information, check LLVM debug-info", () => { for (var idx in commands) {
const commands = [ const command = commands[idx]
`resolc -g ${paths.pathToBasicSolContract} --overwrite --debug-output-dir="${paths.pathToOutputDir}"`, const result = executeCommand(command)
`resolc -g --disable-solc-optimizer ${paths.pathToBasicSolContract} --overwrite --debug-output-dir="${paths.pathToOutputDir}"`
]; // potential issue on resolc with full path on Windows cmd`;
for (var idx in commands) { it('Compiler run successful', () => {
const command = commands[idx]; expect(result.output).toMatch(/(Compiler run successful.)/i)
const result = executeCommand(command); })
it('Exit code = 0', () => {
expect(result.exitCode).toBe(0)
})
it('Output dir is created', () => {
expect(isFolderExist(paths.pathToOutputDir)).toBe(true)
})
it('Output files are created', () => {
// a bug on windows
expect(
isFileExist(
paths.pathToOutputDir,
paths.contractOptimizedLLVMFilename,
paths.llvmExtension
)
).toBe(true)
expect(
isFileExist(
paths.pathToOutputDir,
paths.contractUnoptimizedLLVMFilename,
paths.llvmExtension
)
).toBe(true)
})
it('the output files are not empty', () => {
expect(isFileEmpty(paths.pathToSolBinOutputFile)).toBe(false)
expect(isFileEmpty(paths.pathToSolAsmOutputFile)).toBe(false)
})
it("No 'Error'/'Fail' in the output", () => {
expect(result.output).not.toMatch(/([Ee]rror|[Ff]ail)/i)
})
}
})
it("Compiler run successful", () => { describe('Standard JSON compilation with path options', () => {
expect(result.output).toMatch(/(Compiler run successful.)/i); const contractsDir = path.join(shell.tempdir(), 'contracts-test')
}); const inputFile = path.join(__dirname, '..', 'src/contracts/compiled/1.json')
it("Exit code = 0", () => {
expect(result.exitCode).toBe(0);
});
it("Output dir is created", () => {
expect(isFolderExist(paths.pathToOutputDir)).toBe(true);
});
it("Output files are created", () => { // a bug on windows
expect(isFileExist(paths.pathToOutputDir, paths.contractOptimizedLLVMFilename, paths.llvmExtension)).toBe(true);
expect(isFileExist(paths.pathToOutputDir, paths.contractUnoptimizedLLVMFilename, paths.llvmExtension)).toBe(true);
});
it("the output files are not empty", () => {
expect(isFileEmpty(paths.pathToSolBinOutputFile)).toBe(false);
expect(isFileEmpty(paths.pathToSolAsmOutputFile)).toBe(false);
});
it("No 'Error'/'Fail' in the output", () => {
expect(result.output).not.toMatch(/([Ee]rror|[Ff]ail)/i);
});
}
});
beforeAll(() => {
shell.mkdir('-p', contractsDir)
const input = JSON.parse(shell.cat(inputFile).toString())
describe("Standard JSON compilation with path options", () => { Object.entries(input.sources).forEach(
const contractsDir = path.join(shell.tempdir(), 'contracts-test'); ([sourcePath, source]: [string, any]) => {
const inputFile = path.join(__dirname, '..', 'src/contracts/compiled/1.json'); const filePath = path.join(contractsDir, sourcePath)
shell.mkdir('-p', path.dirname(filePath))
shell.ShellString(source.content).to(filePath)
}
)
})
afterAll(() => {
shell.rm('-rf', contractsDir)
})
describe('Output with all path options', () => {
let result: { exitCode: number; output: string }
beforeAll(() => { beforeAll(() => {
shell.mkdir('-p', contractsDir); const tempInputFile = path.join(contractsDir, 'temp-input.json')
shell.cp(inputFile, tempInputFile)
const inputContent = shell.cat(inputFile).toString()
const input = JSON.parse(shell.cat(inputFile).toString()); const command = `resolc --standard-json --base-path "${contractsDir}" --include-path "${contractsDir}" --allow-paths "${contractsDir}"`
Object.entries(input.sources).forEach(([sourcePath, source]: [string, any]) => { result = executeCommand(command, inputContent)
const filePath = path.join(contractsDir, sourcePath);
shell.mkdir('-p', path.dirname(filePath));
shell.ShellString(source.content).to(filePath);
});
});
afterAll(() => { shell.rm(tempInputFile)
shell.rm('-rf', contractsDir); })
});
describe("Output with all path options", () => { it('Compiler run successful without emiting warnings', () => {
let result: { exitCode: number; output: string }; const parsedResults = JSON.parse(result.output)
expect(
beforeAll(() => { parsedResults.errors.filter(
const tempInputFile = path.join(contractsDir, 'temp-input.json'); (error: { type: string }) => error.type != 'Warning'
shell.cp(inputFile, tempInputFile); )
const inputContent = shell.cat(inputFile).toString(); ).toEqual([])
})
const command = `resolc --standard-json --base-path "${contractsDir}" --include-path "${contractsDir}" --allow-paths "${contractsDir}"`; })
})
result = executeCommand(command, inputContent);
shell.rm(tempInputFile);
});
it("Compiler run successful without emiting warnings", () => {
const parsedResults = JSON.parse(result.output)
expect(parsedResults.errors.filter((error: { type: string; }) => error.type != 'Warning')).toEqual([]);
});
});
});
@@ -1,40 +1,39 @@
import {executeCommand} from "../src/helper"; import { executeCommand } from '../src/helper'
import { paths } from '../src/entities'; import { paths } from '../src/entities'
//id1743 //id1743
describe("Run with --yul by default", () => { describe('Run with --yul by default', () => {
const command = `resolc ${paths.pathToBasicYulContract} --yul`; const command = `resolc ${paths.pathToBasicYulContract} --yul`
const result = executeCommand(command); const result = executeCommand(command)
const commandInvalid = 'resolc --yul'; const commandInvalid = 'resolc --yul'
const resultInvalid = executeCommand(commandInvalid); const resultInvalid = executeCommand(commandInvalid)
it("Valid command exit code = 0", () => { it('Valid command exit code = 0', () => {
expect(result.exitCode).toBe(0); expect(result.exitCode).toBe(0)
}); })
it("--yul output is presented", () => { it('--yul output is presented', () => {
expect(result.output).toMatch(/(Compiler run successful)/i); expect(result.output).toMatch(/(Compiler run successful)/i)
expect(result.output).toMatch(/(No output requested)/i); expect(result.output).toMatch(/(No output requested)/i)
}); })
xit('solc exit code == resolc exit code', () => {
// unknown solc issue for datatype of the contract
const command = `solc ${paths.pathToBasicSolContract} --yul`
const solcResult = executeCommand(command)
expect(solcResult.exitCode).toBe(result.exitCode)
})
xit("solc exit code == resolc exit code", () => { // unknown solc issue for datatype of the contract it('run invalid: resolc --yul', () => {
const command = `solc ${paths.pathToBasicSolContract} --yul`; expect(resultInvalid.output).toMatch(/(The input file is missing)/i)
const solcResult = executeCommand(command); })
expect(solcResult.exitCode).toBe(result.exitCode); it('Invalid command exit code = 1', () => {
}); expect(resultInvalid.exitCode).toBe(1)
})
it("run invalid: resolc --yul", () => { it('Invalid solc exit code == Invalid resolc exit code', () => {
expect(resultInvalid.output).toMatch(/(The input file is missing)/i); const command = 'solc --yul'
}); const solcResult = executeCommand(command)
it("Invalid command exit code = 1", () => { expect(solcResult.exitCode).toBe(resultInvalid.exitCode)
expect(resultInvalid.exitCode).toBe(1); })
}); })
it("Invalid solc exit code == Invalid resolc exit code", () => {
const command = 'solc --yul';
const solcResult = executeCommand(command);
expect(solcResult.exitCode).toBe(resultInvalid.exitCode);
});
});
+159 -124
View File
@@ -1,153 +1,188 @@
import { executeCommand } from "../src/helper"; import { executeCommand } from '../src/helper'
import { paths } from '../src/entities'; import { paths } from '../src/entities'
describe('Set of --combined-json tests', () => {
const zksolcCommand = 'zksolc'
const solcCommand = 'solc'
const json_args: string[] = [
`abi`,
`hashes`,
`metadata`,
`devdoc`,
`userdoc`,
`storage-layout`,
`ast`,
`asm`,
`bin`,
`bin-runtime`,
]
describe("Set of --combined-json tests", () => { //id1742:I
const zksolcCommand = 'zksolc'; describe(`Run ${zksolcCommand} with just --combined-json`, () => {
const solcCommand = 'solc'; const args = [`--combined-json`]
const json_args: string[] = [`abi`, `hashes`, `metadata`, `devdoc`, `userdoc`, `storage-layout`, `ast`, `asm`, `bin`, `bin-runtime`]; const result = executeCommand(zksolcCommand, args)
//id1742:I it('Valid command exit code = 1', () => {
describe(`Run ${zksolcCommand} with just --combined-json`, () => { expect(result.exitCode).toBe(1)
const args = [`--combined-json`]; })
const result = executeCommand(zksolcCommand, args);
it("Valid command exit code = 1", () => { it('--combined-json error is presented', () => {
expect(result.exitCode).toBe(1); expect(result.output).toMatch(/(requires a value but none was supplied)/i)
}); })
it("--combined-json error is presented", () => { it('solc exit code == zksolc exit code', () => {
expect(result.output).toMatch(/(requires a value but none was supplied)/i); const solcResult = executeCommand(solcCommand, args)
}); expect(solcResult.exitCode).toBe(result.exitCode)
})
})
it("solc exit code == zksolc exit code", () => { //id1742:II
const solcResult = executeCommand(solcCommand, args); describe(`Run ${zksolcCommand} with Sol contract and --combined-json`, () => {
expect(solcResult.exitCode).toBe(result.exitCode); const args = [`${paths.pathToBasicSolContract}`, `--combined-json`]
}); const result = executeCommand(zksolcCommand, args)
});
//id1742:II it('Valid command exit code = 1', () => {
describe(`Run ${zksolcCommand} with Sol contract and --combined-json`, () => { expect(result.exitCode).toBe(1)
const args = [`${paths.pathToBasicSolContract}`, `--combined-json`]; })
const result = executeCommand(zksolcCommand, args);
it("Valid command exit code = 1", () => { it('--combined-json error is presented', () => {
expect(result.exitCode).toBe(1); expect(result.output).toMatch(/(requires a value but none was supplied)/i)
}); })
it("--combined-json error is presented", () => { it('solc exit code == zksolc exit code', () => {
expect(result.output).toMatch(/(requires a value but none was supplied)/i); const solcResult = executeCommand(solcCommand, args)
}); expect(solcResult.exitCode).toBe(result.exitCode)
})
})
it("solc exit code == zksolc exit code", () => { //id1742:III
const solcResult = executeCommand(solcCommand, args); for (let i = 0; i < json_args.length; i++) {
expect(solcResult.exitCode).toBe(result.exitCode); describe(`Run ${zksolcCommand} with Sol, --combined-json and ARG: ${json_args[i]}`, () => {
}); const args = [
}); `${paths.pathToBasicSolContract}`,
`--combined-json`,
`${json_args[i]}`,
]
const result = executeCommand(zksolcCommand, args)
//id1742:III it('Valid command exit code = 0', () => {
for (let i = 0; i < json_args.length; i++) { expect(result.exitCode).toBe(0)
describe(`Run ${zksolcCommand} with Sol, --combined-json and ARG: ${json_args[i]}`, () => { })
const args = [`${paths.pathToBasicSolContract}`, `--combined-json`, `${json_args[i]}`];
const result = executeCommand(zksolcCommand, args);
it("Valid command exit code = 0", () => { it('--combined-json error is presented', () => {
expect(result.exitCode).toBe(0); expect(result.output).toMatch(/(contracts)/i)
}); })
it("--combined-json error is presented", () => { it('solc exit code == zksolc exit code', () => {
expect(result.output).toMatch(/(contracts)/i); const solcResult = executeCommand(solcCommand, args)
}); expect(solcResult.exitCode).toBe(result.exitCode)
})
})
}
it("solc exit code == zksolc exit code", () => { //id1829:I
const solcResult = executeCommand(solcCommand, args); for (let i = 0; i < json_args.length; i++) {
expect(solcResult.exitCode).toBe(result.exitCode); describe(`Run ${zksolcCommand} with Sol, --combined-json and wrong ARG: --${json_args[i]}`, () => {
}); const args = [
}); `${paths.pathToBasicSolContract}`,
} `--combined-json`,
`--${json_args[i]}`,
]
const result = executeCommand(zksolcCommand, args)
//id1829:I it('Valid command exit code = 1', () => {
for (let i = 0; i < json_args.length; i++) { expect(result.exitCode).toBe(1)
describe(`Run ${zksolcCommand} with Sol, --combined-json and wrong ARG: --${json_args[i]}`, () => { })
const args = [`${paths.pathToBasicSolContract}`, `--combined-json`, `--${json_args[i]}`];
const result = executeCommand(zksolcCommand, args);
it("Valid command exit code = 1", () => { it('--combined-json error is presented', () => {
expect(result.exitCode).toBe(1); expect(result.output).toMatch(/(Invalid option|error)/i)
}); })
it("--combined-json error is presented", () => { it('solc exit code == zksolc exit code', () => {
expect(result.output).toMatch(/(Invalid option|error)/i); const solcResult = executeCommand(solcCommand, args)
}); expect(solcResult.exitCode).toBe(result.exitCode)
})
})
}
it("solc exit code == zksolc exit code", () => { //id1829:II
const solcResult = executeCommand(solcCommand, args); for (let i = 0; i < json_args.length; i++) {
expect(solcResult.exitCode).toBe(result.exitCode); describe(`Run ${zksolcCommand} with Sol, --combined-json and multiple ARG: ${json_args[i]} ${json_args[i]}`, () => {
}); const args = [
}); `${paths.pathToBasicSolContract}`,
} `--combined-json`,
`${json_args[i]}`,
`${json_args[i]}`,
]
const result = executeCommand(zksolcCommand, args)
//id1829:II xit('Valid command exit code = 1', () => {
for (let i = 0; i < json_args.length; i++) { expect(result.exitCode).toBe(1)
describe(`Run ${zksolcCommand} with Sol, --combined-json and multiple ARG: ${json_args[i]} ${json_args[i]}`, () => { })
const args = [`${paths.pathToBasicSolContract}`, `--combined-json`, `${json_args[i]}`, `${json_args[i]}`];
const result = executeCommand(zksolcCommand, args);
xit("Valid command exit code = 1", () => { it('--combined-json error is presented', () => {
expect(result.exitCode).toBe(1); expect(result.output).toMatch(
}); /(No such file or directory|cannot find the file specified)/i
) // Hopefully we should have more precise message here!
})
it("--combined-json error is presented", () => { xit('solc exit code == zksolc exit code', () => {
expect(result.output).toMatch(/(No such file or directory|cannot find the file specified)/i); // Hopefully we should have more precise message here! const solcResult = executeCommand(solcCommand, args)
}); expect(solcResult.exitCode).toBe(result.exitCode)
})
})
}
xit("solc exit code == zksolc exit code", () => { //id1829:III
const solcResult = executeCommand(solcCommand, args); for (let i = 0; i < json_args.length; i++) {
expect(solcResult.exitCode).toBe(result.exitCode); describe(`Run ${zksolcCommand} with Sol, and multiple (--combined-json ${json_args[i]})`, () => {
}); const args = [
}); `${paths.pathToBasicSolContract}`,
} `--combined-json`,
`${json_args[i]}`,
`--combined-json`,
`${json_args[i]}`,
]
const result = executeCommand(zksolcCommand, args)
//id1829:III it('Valid command exit code = 1', () => {
for (let i = 0; i < json_args.length; i++) { expect(result.exitCode).toBe(1)
describe(`Run ${zksolcCommand} with Sol, and multiple (--combined-json ${json_args[i]})`, () => { })
const args = [`${paths.pathToBasicSolContract}`, `--combined-json`, `${json_args[i]}`, `--combined-json`, `${json_args[i]}`];
const result = executeCommand(zksolcCommand, args);
it("Valid command exit code = 1", () => { it('--combined-json error is presented', () => {
expect(result.exitCode).toBe(1); expect(result.output).toMatch(/(cannot be used multiple times)/i)
}); })
it("--combined-json error is presented", () => { it('solc exit code == zksolc exit code', () => {
expect(result.output).toMatch(/(cannot be used multiple times)/i); const solcResult = executeCommand(solcCommand, args)
}); expect(solcResult.exitCode).toBe(result.exitCode)
})
})
}
it("solc exit code == zksolc exit code", () => { //id1830
const solcResult = executeCommand(solcCommand, args); for (let i = 0; i < json_args.length; i++) {
expect(solcResult.exitCode).toBe(result.exitCode); describe(`Run ${zksolcCommand} with Yul, and --combined-json ${json_args[i]}`, () => {
}); const args = [
}); `${paths.pathToBasicYulContract}`,
} `--combined-json`,
`${json_args[i]}`,
]
const result = executeCommand(zksolcCommand, args)
//id1830 it('Valid command exit code = 1', () => {
for (let i = 0; i < json_args.length; i++) { expect(result.exitCode).toBe(1)
describe(`Run ${zksolcCommand} with Yul, and --combined-json ${json_args[i]}`, () => { })
const args = [`${paths.pathToBasicYulContract}`, `--combined-json`, `${json_args[i]}`];
const result = executeCommand(zksolcCommand, args);
it("Valid command exit code = 1", () => { it('--combined-json error is presented', () => {
expect(result.exitCode).toBe(1); expect(result.output).toMatch(/(ParserError: Expected identifier)/i)
}); })
asd
it("--combined-json error is presented", () => { it('solc exit code == zksolc exit code', () => {
expect(result.output).toMatch(/(ParserError: Expected identifier)/i); const solcResult = executeCommand(solcCommand, args)
}); expect(solcResult.exitCode).toBe(result.exitCode)
asd })
})
it("solc exit code == zksolc exit code", () => { }
const solcResult = executeCommand(solcCommand, args); })
expect(solcResult.exitCode).toBe(result.exitCode);
});
});
}
});
@@ -121,6 +121,11 @@ where
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
context.set_debug_location(self.location.line, 0, None)?; context.set_debug_location(self.location.line, 0, None)?;
let value = match self.initializer.into_llvm(context)? {
Some(value) => value,
None => return Ok(()),
};
if self.bindings.len() == 1 { if self.bindings.len() == 1 {
let identifier = self.bindings.remove(0); let identifier = self.bindings.remove(0);
let pointer = context let pointer = context
@@ -134,27 +139,10 @@ where
identifier.inner, identifier.inner,
) )
})?; })?;
context.build_store(pointer, value.access(context)?)?;
let mut assignment_pointer = Some(pointer.value);
let value = match self
.initializer
.into_llvm(context, &mut assignment_pointer)?
{
Some(value) => value,
None => return Ok(()),
};
if assignment_pointer.is_some() {
context.build_store(pointer, value.access(context)?)?;
}
return Ok(()); return Ok(());
} }
let value = match self.initializer.into_llvm(context, &mut None)? {
Some(value) => value,
None => return Ok(()),
};
let value = value.access(context)?; let value = value.access(context)?;
let llvm_type = value.into_struct_value().get_type(); let llvm_type = value.into_struct_value().get_type();
let tuple_pointer = context.build_alloca(llvm_type, "assignment_pointer"); let tuple_pointer = context.build_alloca(llvm_type, "assignment_pointer");
@@ -184,7 +184,7 @@ where
block.into_llvm(context)?; block.into_llvm(context)?;
} }
Statement::Expression(expression) => { Statement::Expression(expression) => {
expression.into_llvm(context, &mut None)?; expression.into_llvm(context)?;
} }
Statement::VariableDeclaration(statement) => statement.into_llvm(context)?, Statement::VariableDeclaration(statement) => statement.into_llvm(context)?,
Statement::Assignment(statement) => statement.into_llvm(context)?, Statement::Assignment(statement) => statement.into_llvm(context)?,
@@ -118,7 +118,6 @@ impl FunctionCall {
pub fn into_llvm<'ctx, D>( pub fn into_llvm<'ctx, D>(
mut self, mut self,
context: &mut revive_llvm_context::PolkaVMContext<'ctx, D>, context: &mut revive_llvm_context::PolkaVMContext<'ctx, D>,
assignment_pointer: &mut Option<inkwell::values::PointerValue<'ctx>>,
) -> anyhow::Result<Option<inkwell::values::BasicValueEnum<'ctx>>> ) -> anyhow::Result<Option<inkwell::values::BasicValueEnum<'ctx>>>
where where
D: revive_llvm_context::PolkaVMDependency + Clone, D: revive_llvm_context::PolkaVMDependency + Clone,
@@ -130,7 +129,7 @@ impl FunctionCall {
let mut values = Vec::with_capacity(self.arguments.len()); let mut values = Vec::with_capacity(self.arguments.len());
for argument in self.arguments.into_iter().rev() { for argument in self.arguments.into_iter().rev() {
let value = argument let value = argument
.into_llvm(context, &mut None)? .into_llvm(context)?
.expect("Always exists") .expect("Always exists")
.access(context)?; .access(context)?;
values.push(value); values.push(value);
@@ -417,8 +416,8 @@ impl FunctionCall {
revive_llvm_context::polkavm_evm_memory::load( revive_llvm_context::polkavm_evm_memory::load(
context, context,
arguments[0].into_int_value(), arguments[0].into_int_value(),
assignment_pointer,
) )
.map(Some)
} }
Name::MStore => { Name::MStore => {
let arguments = self.pop_arguments_llvm::<D, 2>(context)?; let arguments = self.pop_arguments_llvm::<D, 2>(context)?;
@@ -466,11 +465,7 @@ impl FunctionCall {
Name::SLoad => { Name::SLoad => {
let arguments = self.pop_arguments::<D, 1>(context)?; let arguments = self.pop_arguments::<D, 1>(context)?;
revive_llvm_context::polkavm_evm_storage::load( revive_llvm_context::polkavm_evm_storage::load(context, &arguments[0]).map(Some)
context,
&arguments[0],
assignment_pointer,
)
} }
Name::SStore => { Name::SStore => {
let arguments = self.pop_arguments::<D, 2>(context)?; let arguments = self.pop_arguments::<D, 2>(context)?;
@@ -994,7 +989,7 @@ impl FunctionCall {
for expression in self.arguments.drain(0..N).rev() { for expression in self.arguments.drain(0..N).rev() {
arguments.push( arguments.push(
expression expression
.into_llvm(context, &mut None)? .into_llvm(context)?
.expect("Always exists") .expect("Always exists")
.access(context)?, .access(context)?,
); );
@@ -1014,11 +1009,7 @@ impl FunctionCall {
{ {
let mut arguments = Vec::with_capacity(N); let mut arguments = Vec::with_capacity(N);
for expression in self.arguments.drain(0..N).rev() { for expression in self.arguments.drain(0..N).rev() {
arguments.push( arguments.push(expression.into_llvm(context)?.expect("Always exists"));
expression
.into_llvm(context, &mut None)?
.expect("Always exists"),
);
} }
arguments.reverse(); arguments.reverse();
@@ -101,7 +101,6 @@ impl Expression {
pub fn into_llvm<'ctx, D>( pub fn into_llvm<'ctx, D>(
self, self,
context: &mut revive_llvm_context::PolkaVMContext<'ctx, D>, context: &mut revive_llvm_context::PolkaVMContext<'ctx, D>,
assignment_pointer: &mut Option<inkwell::values::PointerValue<'ctx>>,
) -> anyhow::Result<Option<revive_llvm_context::PolkaVMArgument<'ctx>>> ) -> anyhow::Result<Option<revive_llvm_context::PolkaVMArgument<'ctx>>>
where where
D: revive_llvm_context::PolkaVMDependency + Clone, D: revive_llvm_context::PolkaVMDependency + Clone,
@@ -140,7 +139,7 @@ impl Expression {
})) }))
} }
Self::FunctionCall(call) => Ok(call Self::FunctionCall(call) => Ok(call
.into_llvm(context, assignment_pointer)? .into_llvm(context)?
.map(revive_llvm_context::PolkaVMArgument::value)), .map(revive_llvm_context::PolkaVMArgument::value)),
} }
} }
@@ -76,7 +76,7 @@ where
context.set_basic_block(condition_block); context.set_basic_block(condition_block);
let condition = self let condition = self
.condition .condition
.into_llvm(context, &mut None)? .into_llvm(context)?
.expect("Always exists") .expect("Always exists")
.access(context)? .access(context)?
.into_int_value(); .into_int_value();
@@ -55,7 +55,7 @@ where
fn into_llvm(self, context: &mut revive_llvm_context::PolkaVMContext<D>) -> anyhow::Result<()> { fn into_llvm(self, context: &mut revive_llvm_context::PolkaVMContext<D>) -> anyhow::Result<()> {
let condition = self let condition = self
.condition .condition
.into_llvm(context, &mut None)? .into_llvm(context)?
.expect("Always exists") .expect("Always exists")
.access(context)? .access(context)?
.into_int_value(); .into_int_value();
@@ -209,6 +209,8 @@ where
revive_llvm_context::PolkaVMRemainderFunction.declare(context)?; revive_llvm_context::PolkaVMRemainderFunction.declare(context)?;
revive_llvm_context::PolkaVMSignedRemainderFunction.declare(context)?; revive_llvm_context::PolkaVMSignedRemainderFunction.declare(context)?;
revive_llvm_context::PolkaVMSbrkFunction.declare(context)?;
let mut entry = revive_llvm_context::PolkaVMEntryFunction::default(); let mut entry = revive_llvm_context::PolkaVMEntryFunction::default();
entry.declare(context)?; entry.declare(context)?;
@@ -261,6 +263,8 @@ where
revive_llvm_context::PolkaVMRemainderFunction.into_llvm(context)?; revive_llvm_context::PolkaVMRemainderFunction.into_llvm(context)?;
revive_llvm_context::PolkaVMSignedRemainderFunction.into_llvm(context)?; revive_llvm_context::PolkaVMSignedRemainderFunction.into_llvm(context)?;
revive_llvm_context::PolkaVMSbrkFunction.into_llvm(context)?;
Ok(()) Ok(())
} }
@@ -123,7 +123,7 @@ where
D: revive_llvm_context::PolkaVMDependency + Clone, D: revive_llvm_context::PolkaVMDependency + Clone,
{ {
fn into_llvm(self, context: &mut revive_llvm_context::PolkaVMContext<D>) -> anyhow::Result<()> { fn into_llvm(self, context: &mut revive_llvm_context::PolkaVMContext<D>) -> anyhow::Result<()> {
let scrutinee = self.expression.into_llvm(context, &mut None)?; let scrutinee = self.expression.into_llvm(context)?;
if self.cases.is_empty() { if self.cases.is_empty() {
if let Some(block) = self.default { if let Some(block) = self.default {
@@ -110,9 +110,8 @@ where
.borrow_mut() .borrow_mut()
.insert_stack_pointer(identifier.inner.clone(), pointer); .insert_stack_pointer(identifier.inner.clone(), pointer);
let mut assignment_pointer = Some(pointer.value);
let value = if let Some(expression) = self.expression { let value = if let Some(expression) = self.expression {
match expression.into_llvm(context, &mut assignment_pointer)? { match expression.into_llvm(context)? {
Some(mut value) => { Some(mut value) => {
if let Some(constant) = value.constant.take() { if let Some(constant) = value.constant.take() {
context context
@@ -129,9 +128,7 @@ where
} else { } else {
r#type.const_zero().as_basic_value_enum() r#type.const_zero().as_basic_value_enum()
}; };
if assignment_pointer.is_some() { context.build_store(pointer, value)?;
context.build_store(pointer, value)?;
}
return Ok(()); return Ok(());
} }
@@ -159,7 +156,7 @@ where
None => return Ok(()), None => return Ok(()),
}; };
let location = expression.location(); let location = expression.location();
let expression = match expression.into_llvm(context, &mut None)? { let expression = match expression.into_llvm(context)? {
Some(expression) => expression, Some(expression) => expression,
None => return Ok(()), None => return Ok(()),
}; };
+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"; process.env.RELEASE_RESOLC_WASM_URI || "http://127.0.0.1:8080/resolc.wasm";
const RESOLC_WASM_TARGET_DIR = path.join( const RESOLC_WASM_TARGET_DIR = path.join(
__dirname, __dirname,
"../target/wasm32-unknown-emscripten/release", "../../target/wasm32-unknown-emscripten/release",
); );
const RESOLC_JS = path.join(RESOLC_WASM_TARGET_DIR, "resolc.js"); const RESOLC_JS = path.join(RESOLC_WASM_TARGET_DIR, "resolc.js");
const RESOLC_WEB_JS = path.join(RESOLC_WASM_TARGET_DIR, "resolc_web.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": { "outputSelection": {
"*": { "*": {
"*": [ "*": ["abi"]
"abi"
]
} }
} }
} }
+138
View File
@@ -0,0 +1,138 @@
import { expect } from 'chai'
import { compile } from '../examples/node/revive.js'
import { fileURLToPath } from 'url'
import path from 'path'
import fs from 'fs'
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
function loadFixture(fixture) {
const fixturePath = path.resolve(__dirname, `../fixtures/${fixture}`)
return JSON.parse(fs.readFileSync(fixturePath, 'utf-8'))
}
describe('Compile Function Tests', function () {
it('should successfully compile valid Solidity code', async function () {
const standardInput = loadFixture('storage.json')
const result = await compile(standardInput)
expect(result).to.be.a('string')
const output = JSON.parse(result)
expect(output).to.have.property('contracts')
expect(output.contracts['fixtures/storage.sol']).to.have.property('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.evm
).to.have.property('bytecode')
})
if (typeof globalThis.Bun == 'undefined') {
// Running this test with Bun on a Linux host causes:
// 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.
it('should successfully compile large Solidity code', async function () {
const standardInput = loadFixture('token.json')
const result = await compile(standardInput)
expect(result).to.be.a('string')
const output = JSON.parse(result)
expect(output).to.have.property('contracts')
expect(output.contracts['fixtures/token.sol']).to.have.property('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.evm
).to.have.property('bytecode')
})
it('should successfully compile a valid Solidity contract that instantiates the token contracts', async function () {
const standardInput = loadFixture('instantiate_tokens.json')
const result = await compile(standardInput)
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'].TokensFactory.evm
).to.have.property('bytecode')
})
}
it('should throw an error for invalid Solidity code', async function () {
const standardInput = loadFixture('invalid_contract_content.json')
const result = await compile(standardInput)
expect(result).to.be.a('string')
const output = JSON.parse(result)
expect(output).to.have.property('errors')
expect(output.errors).to.be.an('array')
expect(output.errors.length).to.be.greaterThan(0)
expect(output.errors[0].type).to.exist
expect(output.errors[0].type).to.contain('ParserError')
})
it('should return not found error for missing imports', async function () {
const standardInput = loadFixture('missing_import.json')
const result = await compile(standardInput)
const output = JSON.parse(result)
expect(output).to.have.property('errors')
expect(output.errors).to.be.an('array')
expect(output.errors.length).to.be.greaterThan(0)
expect(output.errors[0].message).to.exist
expect(output.errors[0].message).to.include(
'Source "nonexistent/console.sol" not found'
)
})
it('should successfully compile a valid Solidity contract that instantiates another contract', async function () {
const standardInput = loadFixture('instantiate.json')
const result = await compile(standardInput)
expect(result).to.be.a('string')
const output = JSON.parse(result)
expect(output).to.have.property('contracts')
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.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.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"
}
}
+200
View File
@@ -0,0 +1,200 @@
#!/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)
})
+116
View File
@@ -0,0 +1,116 @@
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}`
)
}
}
})
+213
View File
@@ -0,0 +1,213 @@
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: {
optimizer?: Record<string, unknown>
bin?: string
} = {}
): Promise<SolcOutput> {
const {
optimizer = {
mode: 'z',
fallback_to_optimizing_for_size: true,
enabled: true,
runs: 200,
},
bin,
} = option
const input = JSON.stringify({
language: 'Solidity',
sources: resolveInputs(sources),
settings: {
optimizer,
outputSelection: {
'*': {
'*': ['abi'],
},
},
},
})
if (bin) {
return compileWithBin(input, 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/**/*"]
}
-143
View File
@@ -1,143 +0,0 @@
import { expect } from "chai";
import { compile } from "../examples/node/revive.js";
import { fileURLToPath } from "url";
import path from "path";
import fs from "fs";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
function loadFixture(fixture) {
const fixturePath = path.resolve(__dirname, `../fixtures/${fixture}`);
return JSON.parse(fs.readFileSync(fixturePath, "utf-8"));
}
describe("Compile Function Tests", function () {
it("should successfully compile valid Solidity code", async function () {
const standardInput = loadFixture("storage.json");
const result = await compile(standardInput);
expect(result).to.be.a("string");
const output = JSON.parse(result);
expect(output).to.have.property("contracts");
expect(output.contracts["fixtures/storage.sol"]).to.have.property(
"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.evm,
).to.have.property("bytecode");
});
if (typeof globalThis.Bun == "undefined") {
// Running this test with Bun on a Linux host causes:
// 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.
it("should successfully compile large Solidity code", async function () {
const standardInput = loadFixture("token.json");
const result = await compile(standardInput);
expect(result).to.be.a("string");
const output = JSON.parse(result);
expect(output).to.have.property("contracts");
expect(output.contracts["fixtures/token.sol"]).to.have.property(
"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.evm,
).to.have.property("bytecode");
});
it("should successfully compile a valid Solidity contract that instantiates the token contracts", async function () {
const standardInput = loadFixture("instantiate_tokens.json");
const result = await compile(standardInput);
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"].TokensFactory.evm,
).to.have.property("bytecode");
});
}
it("should throw an error for invalid Solidity code", async function () {
const standardInput = loadFixture("invalid_contract_content.json");
const result = await compile(standardInput);
expect(result).to.be.a("string");
const output = JSON.parse(result);
expect(output).to.have.property("errors");
expect(output.errors).to.be.an("array");
expect(output.errors.length).to.be.greaterThan(0);
expect(output.errors[0].type).to.exist;
expect(output.errors[0].type).to.contain("ParserError");
});
it("should return not found error for missing imports", async function () {
const standardInput = loadFixture("missing_import.json");
const result = await compile(standardInput);
const output = JSON.parse(result);
expect(output).to.have.property("errors");
expect(output.errors).to.be.an("array");
expect(output.errors.length).to.be.greaterThan(0);
expect(output.errors[0].message).to.exist;
expect(output.errors[0].message).to.include(
'Source "nonexistent/console.sol" not found',
);
});
it("should successfully compile a valid Solidity contract that instantiates another contract", async function () {
const standardInput = loadFixture("instantiate.json");
const result = await compile(standardInput);
expect(result).to.be.a("string");
const output = JSON.parse(result);
expect(output).to.have.property("contracts");
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.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.evm,
).to.have.property("bytecode");
});
});
+6618
View File
File diff suppressed because it is too large Load Diff
+20 -12
View File
@@ -1,13 +1,21 @@
{ {
"name": "root", "name": "root",
"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:node -w js", "test:wasm": "npm run test:node -w js/emscripten",
"build:package": "npm run build:package -w js" "build:package": "npm run build:package -w js/emscripten",
}, "lint": "npx eslint 'js/**/*.{cjs,mjs,ts}' && npx prettier --check '**/*.{mjs,cjs,ts}'",
"workspaces": [ "lint:fix": "npx prettier --write '**/*.{mjs,cjs,ts}'"
"crates/solidity/src/tests/cli-tests", },
"js" "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"
}
}