Compare commits

...

9 Commits

Author SHA1 Message Date
Cyrill Leutwiler 0685df37a1 the revive call function
Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
2025-07-04 12:31:25 +02:00
Cyrill Leutwiler 3204b61ea0 call reentrancy heuristic function
Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
2025-07-04 12:01:10 +02:00
xermicus 4028f7a985 outline all arithmetic and bitwise functions
Signed-off-by: xermicus <cyrill@parity.io>
2025-07-03 16:03:40 +02:00
xermicus ed608699af release resolc v0.3.0 (#354)
Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
2025-06-28 12:32:48 +02:00
xermicus 75fc23c810 add the missing memset builtin (#353)
Closes  #350

- Add the missing `memset` builtin which was accidentally deleted in a
previous PR.
- Add a compilation test to ensure the `memset` builtin is present.

---------

Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
2025-06-28 12:01:34 +02:00
xermicus 486c9c28a1 new clippies (#352)
Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
2025-06-28 11:48:24 +02:00
PG Herveou 7656c6a8b4 js: Add stats and override options (#348)
- Add `RESOLC_BIN` env variable to force use a binary compiler instead
of the wasm
- Add `--diff-stats` options to print file path 

e.g

```
~/github/redstone-oracles-monorepo main*
❯ npx @parity/resolc@latest --diff-stats \
  --base-path . \
  --include-path node_modules \
  --bin packages/evm-connector/contracts/samples/SampleWithEvents.sol
┌─────────┬──────────────────────────────────────────────────────────────────────────┬───────────────────────────────┬────────────┬────────────┬───────────┐
│ (index) │ file                                                                     │ contract                      │ polkavm    │ bin        │ diff      │
├─────────┼──────────────────────────────────────────────────────────────────────────┼───────────────────────────────┼────────────┼────────────┼───────────┤
│ 0       │ 'packages/evm-connector/contracts/core/CalldataExtractor.sol'            │ 'CalldataExtractor'           │ '4.31 kB'  │ '2.53 kB'  │ '70.12%'  │
│ 1       │ 'packages/evm-connector/contracts/core/RedstoneConstants.sol'            │ 'RedstoneConstants'           │ '0.80 kB'  │ '0.17 kB'  │ '368.18%' │
│ 2       │ 'packages/evm-connector/contracts/core/RedstoneDefaultsLib.sol'          │ 'RedstoneDefaultsLib'         │ '0.90 kB'  │ '0.31 kB'  │ '189.06%' │
│ 3       │ 'packages/evm-connector/contracts/libs/BitmapLib.sol'                    │ 'BitmapLib'                   │ '0.90 kB'  │ '0.31 kB'  │ '189.06%' │
│ 4       │ 'packages/evm-connector/contracts/libs/NumericArrayLib.sol'              │ 'NumericArrayLib'             │ '0.90 kB'  │ '0.31 kB'  │ '189.06%' │
│ 5       │ 'packages/evm-connector/contracts/libs/SignatureLib.sol'                 │ 'SignatureLib'                │ '0.90 kB'  │ '0.31 kB'  │ '189.06%' │
│ 6       │ 'packages/evm-connector/contracts/mocks/RedstoneConsumerNumericMock.sol' │ 'RedstoneConsumerNumericMock' │ '13.62 kB' │ '10.17 kB' │ '33.96%'  │
│ 7       │ 'packages/evm-connector/contracts/samples/SampleWithEvents.sol'          │ 'SampleWithEvents'            │ '29.34 kB' │ '15.60 kB' │ '88.03%'  │
└─────────┴──────────────────────────────────────────────────────────────────────────┴───────────────────────────────┴────────────┴────────────┴───────────┘
```
2025-06-19 12:17:09 +02:00
xermicus 8754d802fa llvm-context: bugfix PHI values in SAR builtin translation (#345)
Closes #344

Signed-off-by: xermicus <cyrill@parity.io>
2025-06-14 15:12:03 +02:00
PG Herveou 63f0266fff @parity/resolc fix sol file resolutions (#343)
second take on #339 

turns out that the resolve-pkg npm package was also failing to properly
resolve package with an exports field define in the package.json.

This fixes it and add a test case with such a package
2025-06-05 10:05:53 +02:00
49 changed files with 2680 additions and 461 deletions
+13
View File
@@ -6,6 +6,19 @@ This is a development pre-release.
Supported `polkadot-sdk` rev: `2503.0.1` Supported `polkadot-sdk` rev: `2503.0.1`
## v0.3.0
This is a development pre-release.
Supported `polkadot-sdk` rev: `2503.0.1`
### Fixed
- llvm-context: Bugfix the SAR YUL builtin translation.
- runtime-api: Add the missing `memset` builtin.
- npm package: Bugfix the exports field defined in the `package.json`.
## v0.2.0 ## v0.2.0
This is a development pre-release. This is a development pre-release.
Generated
+7 -7
View File
@@ -8546,7 +8546,7 @@ dependencies = [
[[package]] [[package]]
name = "resolc" name = "resolc"
version = "0.2.0" version = "0.3.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"clap", "clap",
@@ -8617,7 +8617,7 @@ dependencies = [
[[package]] [[package]]
name = "revive-integration" name = "revive-integration"
version = "0.1.0" version = "0.1.1"
dependencies = [ dependencies = [
"alloy-primitives 1.1.2", "alloy-primitives 1.1.2",
"alloy-sol-types 1.1.2", "alloy-sol-types 1.1.2",
@@ -8645,7 +8645,7 @@ dependencies = [
[[package]] [[package]]
name = "revive-llvm-builder" name = "revive-llvm-builder"
version = "0.1.0" version = "0.2.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"assert_cmd", "assert_cmd",
@@ -8667,7 +8667,7 @@ dependencies = [
[[package]] [[package]]
name = "revive-llvm-context" name = "revive-llvm-context"
version = "0.2.0" version = "0.3.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"hex", "hex",
@@ -8708,7 +8708,7 @@ dependencies = [
[[package]] [[package]]
name = "revive-runtime-api" name = "revive-runtime-api"
version = "0.1.0" version = "0.2.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"inkwell", "inkwell",
@@ -8730,7 +8730,7 @@ dependencies = [
[[package]] [[package]]
name = "revive-stdlib" name = "revive-stdlib"
version = "0.1.0" version = "0.1.1"
dependencies = [ dependencies = [
"inkwell", "inkwell",
"revive-build-utils", "revive-build-utils",
@@ -8738,7 +8738,7 @@ dependencies = [
[[package]] [[package]]
name = "revive-yul" name = "revive-yul"
version = "0.2.0" version = "0.2.1"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"inkwell", "inkwell",
+6 -6
View File
@@ -14,21 +14,21 @@ repository = "https://github.com/paritytech/revive"
rust-version = "1.85.0" rust-version = "1.85.0"
[workspace.dependencies] [workspace.dependencies]
resolc = { version = "0.2.0", path = "crates/resolc" } resolc = { version = "0.3.0", path = "crates/resolc" }
revive-benchmarks = { version = "0.1.0", path = "crates/benchmarks" } revive-benchmarks = { version = "0.1.0", path = "crates/benchmarks" }
revive-builtins = { version = "0.1.0", path = "crates/builtins" } revive-builtins = { version = "0.1.0", path = "crates/builtins" }
revive-common = { version = "0.1.0", path = "crates/common" } revive-common = { version = "0.1.0", path = "crates/common" }
revive-differential = { version = "0.1.0", path = "crates/differential" } revive-differential = { version = "0.1.0", path = "crates/differential" }
revive-integration = { version = "0.1.0", path = "crates/integration" } revive-integration = { version = "0.1.1", path = "crates/integration" }
revive-linker = { version = "0.1.0", path = "crates/linker" } revive-linker = { version = "0.1.0", path = "crates/linker" }
lld-sys = { version = "0.1.0", path = "crates/lld-sys" } lld-sys = { version = "0.1.0", path = "crates/lld-sys" }
revive-llvm-context = { version = "0.2.0", path = "crates/llvm-context" } revive-llvm-context = { version = "0.3.0", path = "crates/llvm-context" }
revive-runtime-api = { version = "0.1.0", path = "crates/runtime-api" } revive-runtime-api = { version = "0.2.0", path = "crates/runtime-api" }
revive-runner = { version = "0.1.0", path = "crates/runner" } revive-runner = { version = "0.1.0", path = "crates/runner" }
revive-solc-json-interface = { version = "0.2.0", path = "crates/solc-json-interface" } revive-solc-json-interface = { version = "0.2.0", path = "crates/solc-json-interface" }
revive-stdlib = { version = "0.1.0", path = "crates/stdlib" } revive-stdlib = { version = "0.1.1", path = "crates/stdlib" }
revive-build-utils = { version = "0.1.0", path = "crates/build-utils" } revive-build-utils = { version = "0.1.0", path = "crates/build-utils" }
revive-yul = { version = "0.2.0", path = "crates/yul" } revive-yul = { version = "0.2.1", path = "crates/yul" }
hex = "0.4.3" hex = "0.4.3"
cc = "1.2" cc = "1.2"
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "revive-integration" name = "revive-integration"
version.workspace = true version = "0.1.1"
license.workspace = true license.workspace = true
edition.workspace = true edition.workspace = true
repository.workspace = true repository.workspace = true
+49
View File
@@ -0,0 +1,49 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8;
/* runner.json
{
"differential": true,
"actions": [
{
"Instantiate": {
"code": {
"Solidity": {
"contract": "SAR"
}
}
}
}
]
}
*/
contract SAR {
constructor() payable {
assert(sar(0x03, 0x01) == 0x01);
assert(
sar(
0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff,
0x01
) == 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
);
assert(
sar(
0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff,
0xff
) == 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
);
assert(
sar(
0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff,
0x100
) == 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
);
}
function sar(uint256 a, uint256 b) public pure returns (uint256 c) {
assembly {
c := sar(b, a)
}
}
}
+1
View File
@@ -60,6 +60,7 @@ test_spec!(mload, "MLoad", "MLoad.sol");
test_spec!(delegate_no_contract, "DelegateCaller", "DelegateCaller.sol"); test_spec!(delegate_no_contract, "DelegateCaller", "DelegateCaller.sol");
test_spec!(function_type, "FunctionType", "FunctionType.sol"); test_spec!(function_type, "FunctionType", "FunctionType.sol");
test_spec!(layout_at, "LayoutAt", "LayoutAt.sol"); test_spec!(layout_at, "LayoutAt", "LayoutAt.sol");
test_spec!(shift_arithmetic_right, "SAR", "SAR.sol");
fn instantiate(path: &str, contract: &str) -> Vec<SpecsAction> { fn instantiate(path: &str, contract: &str) -> Vec<SpecsAction> {
vec![Instantiate { vec![Instantiate {
+1 -1
View File
@@ -6,7 +6,7 @@ authors = [
"Anton Baliasnikov <aba@matterlabs.dev>", "Anton Baliasnikov <aba@matterlabs.dev>",
"Cyrill Leutwiler <cyrill@parity.io>", "Cyrill Leutwiler <cyrill@parity.io>",
] ]
version.workspace = true version = "0.2.0"
license.workspace = true license.workspace = true
edition.workspace = true edition.workspace = true
repository.workspace = true repository.workspace = true
+1 -1
View File
@@ -22,7 +22,7 @@ impl std::str::FromStr for BuildType {
"Release" => Ok(Self::Release), "Release" => Ok(Self::Release),
"RelWithDebInfo" => Ok(Self::RelWithDebInfo), "RelWithDebInfo" => Ok(Self::RelWithDebInfo),
"MinSizeRel" => Ok(Self::MinSizeRel), "MinSizeRel" => Ok(Self::MinSizeRel),
value => Err(format!("Unsupported build type: `{}`", value)), value => Err(format!("Unsupported build type: `{value}`")),
} }
} }
} }
+1 -1
View File
@@ -16,7 +16,7 @@ impl std::str::FromStr for CcacheVariant {
match value { match value {
"ccache" => Ok(Self::Ccache), "ccache" => Ok(Self::Ccache),
"sccache" => Ok(Self::Sccache), "sccache" => Ok(Self::Sccache),
value => Err(format!("Unsupported ccache variant: `{}`", value)), value => Err(format!("Unsupported ccache variant: `{value}`")),
} }
} }
} }
+13 -14
View File
@@ -127,23 +127,22 @@ pub fn build(
sanitizer: Option<sanitizer::Sanitizer>, sanitizer: Option<sanitizer::Sanitizer>,
enable_valgrind: bool, enable_valgrind: bool,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
log::trace!("build type: {:?}", build_type); log::trace!("build type: {build_type:?}");
log::trace!("target env: {:?}", target_env); log::trace!("target env: {target_env:?}");
log::trace!("targets: {:?}", targets); log::trace!("targets: {targets:?}");
log::trace!("llvm projects: {:?}", llvm_projects); log::trace!("llvm projects: {llvm_projects:?}");
log::trace!("enable rtti: {:?}", enable_rtti); log::trace!("enable rtti: {enable_rtti:?}");
log::trace!("default target: {:?}", default_target); log::trace!("default target: {default_target:?}");
log::trace!("eneable tests: {:?}", enable_tests); log::trace!("eneable tests: {enable_tests:?}");
log::trace!("enable_coverage: {:?}", enable_coverage); log::trace!("enable_coverage: {enable_coverage:?}");
log::trace!("extra args: {:?}", extra_args); log::trace!("extra args: {extra_args:?}");
log::trace!("sanitzer: {:?}", sanitizer); log::trace!("sanitzer: {sanitizer:?}");
log::trace!("enable valgrind: {:?}", enable_valgrind); log::trace!("enable valgrind: {enable_valgrind:?}");
if !PathBuf::from(LLVMPath::DIRECTORY_LLVM_SOURCE).exists() { if !PathBuf::from(LLVMPath::DIRECTORY_LLVM_SOURCE).exists() {
log::error!( log::error!(
"LLVM project source directory {} does not exist (run `revive-llvm --target-env {} clone`)", "LLVM project source directory {} does not exist (run `revive-llvm --target-env {target_env} clone`)",
LLVMPath::DIRECTORY_LLVM_SOURCE, LLVMPath::DIRECTORY_LLVM_SOURCE
target_env
) )
} }
+1 -1
View File
@@ -22,7 +22,7 @@ impl std::str::FromStr for LLVMProject {
"lld" => Ok(Self::LLD), "lld" => Ok(Self::LLD),
"lldb" => Ok(Self::LLDB), "lldb" => Ok(Self::LLDB),
"mlir" => Ok(Self::MLIR), "mlir" => Ok(Self::MLIR),
value => Err(format!("Unsupported LLVM project to enable: `{}`", value)), value => Err(format!("Unsupported LLVM project to enable: `{value}`")),
} }
} }
} }
+1 -1
View File
@@ -30,7 +30,7 @@ impl TryFrom<&PathBuf> for Lock {
fn try_from(path: &PathBuf) -> Result<Self, Self::Error> { fn try_from(path: &PathBuf) -> Result<Self, Self::Error> {
let mut config_str = String::new(); let mut config_str = String::new();
let mut config_file = let mut config_file =
File::open(path).with_context(|| format!("Error opening {:?} file", path))?; File::open(path).with_context(|| format!("Error opening {path:?} file"))?;
config_file.read_to_string(&mut config_str)?; config_file.read_to_string(&mut config_str)?;
Ok(toml::from_str(&config_str)?) Ok(toml::from_str(&config_str)?)
} }
+1 -1
View File
@@ -29,7 +29,7 @@ impl FromStr for Platform {
fn from_str(value: &str) -> Result<Self, Self::Err> { fn from_str(value: &str) -> Result<Self, Self::Err> {
match value { match value {
"PolkaVM" => Ok(Self::PolkaVM), "PolkaVM" => Ok(Self::PolkaVM),
value => Err(format!("Unsupported platform: `{}`", value)), value => Err(format!("Unsupported platform: `{value}`")),
} }
} }
} }
+2 -2
View File
@@ -69,8 +69,8 @@ fn main_inner() -> anyhow::Result<()> {
}) })
.collect(); .collect();
log::debug!("extra_args: {:#?}", extra_args); log::debug!("extra_args: {extra_args:#?}");
log::debug!("extra_args_unescaped: {:#?}", extra_args_unescaped); log::debug!("extra_args_unescaped: {extra_args_unescaped:#?}");
if let Some(ccache_variant) = ccache_variant { if let Some(ccache_variant) = ccache_variant {
revive_llvm_builder::utils::check_presence(ccache_variant.to_string().as_str())?; revive_llvm_builder::utils::check_presence(ccache_variant.to_string().as_str())?;
+1 -1
View File
@@ -30,7 +30,7 @@ impl std::str::FromStr for Sanitizer {
"thread" => Ok(Self::Thread), "thread" => Ok(Self::Thread),
"dataflow" => Ok(Self::DataFlow), "dataflow" => Ok(Self::DataFlow),
"address;undefined" => Ok(Self::AddressUndefined), "address;undefined" => Ok(Self::AddressUndefined),
value => Err(format!("Unsupported sanitizer: `{}`", value)), value => Err(format!("Unsupported sanitizer: `{value}`")),
} }
} }
} }
+1 -1
View File
@@ -20,7 +20,7 @@ impl std::str::FromStr for TargetEnv {
"gnu" => Ok(Self::GNU), "gnu" => Ok(Self::GNU),
"musl" => Ok(Self::MUSL), "musl" => Ok(Self::MUSL),
"emscripten" => Ok(Self::Emscripten), "emscripten" => Ok(Self::Emscripten),
value => Err(format!("Unsupported target environment: `{}`", value)), value => Err(format!("Unsupported target environment: `{value}`")),
} }
} }
} }
+1 -1
View File
@@ -15,7 +15,7 @@ impl std::str::FromStr for TargetTriple {
fn from_str(value: &str) -> Result<Self, Self::Err> { fn from_str(value: &str) -> Result<Self, Self::Err> {
match value { match value {
"polkavm" => Ok(Self::PolkaVM), "polkavm" => Ok(Self::PolkaVM),
value => Err(format!("Unsupported target triple: `{}`", value)), value => Err(format!("Unsupported target triple: `{value}`")),
} }
} }
} }
+2 -2
View File
@@ -92,7 +92,7 @@ pub fn unpack_tar(filename: PathBuf, path: &str) -> anyhow::Result<()> {
pub fn download_musl(name: &str) -> anyhow::Result<()> { pub fn download_musl(name: &str) -> anyhow::Result<()> {
log::info!("downloading musl {name}"); log::info!("downloading musl {name}");
let tar_file_name = format!("{name}.tar.gz"); let tar_file_name = format!("{name}.tar.gz");
let url = format!("{}/{tar_file_name}", MUSL_SNAPSHOTS_URL); let url = format!("{MUSL_SNAPSHOTS_URL}/{tar_file_name}");
let target_path = crate::llvm_path::DIRECTORY_LLVM_TARGET let target_path = crate::llvm_path::DIRECTORY_LLVM_TARGET
.get() .get()
.unwrap() .unwrap()
@@ -223,6 +223,6 @@ pub fn install_emsdk() -> anyhow::Result<()> {
/// The LLVM target directory default path. /// The LLVM target directory default path.
pub fn directory_target_llvm(target_env: crate::target_env::TargetEnv) -> PathBuf { pub fn directory_target_llvm(target_env: crate::target_env::TargetEnv) -> PathBuf {
crate::llvm_path::DIRECTORY_LLVM_TARGET crate::llvm_path::DIRECTORY_LLVM_TARGET
.get_or_init(|| PathBuf::from(format!("./target-llvm/{}/", target_env))) .get_or_init(|| PathBuf::from(format!("./target-llvm/{target_env}/")))
.clone() .clone()
} }
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "revive-llvm-context" name = "revive-llvm-context"
version = "0.2.0" version = "0.3.0"
license.workspace = true license.workspace = true
edition.workspace = true edition.workspace = true
repository.workspace = true repository.workspace = true
+12
View File
@@ -19,10 +19,22 @@ pub use self::polkavm::context::function::declaration::Declaration as PolkaVMFun
pub use self::polkavm::context::function::intrinsics::Intrinsics as PolkaVMIntrinsicFunction; pub use self::polkavm::context::function::intrinsics::Intrinsics as PolkaVMIntrinsicFunction;
pub use self::polkavm::context::function::llvm_runtime::LLVMRuntime as PolkaVMLLVMRuntime; pub use self::polkavm::context::function::llvm_runtime::LLVMRuntime as PolkaVMLLVMRuntime;
pub use self::polkavm::context::function::r#return::Return as PolkaVMFunctionReturn; pub use self::polkavm::context::function::r#return::Return as PolkaVMFunctionReturn;
pub use self::polkavm::context::function::runtime::arithmetics::Addition as PolkaVMAdditionFunction;
pub use self::polkavm::context::function::runtime::arithmetics::Division as PolkaVMDivisionFunction; pub use self::polkavm::context::function::runtime::arithmetics::Division as PolkaVMDivisionFunction;
pub use self::polkavm::context::function::runtime::arithmetics::Multiplication as PolkaVMMultiplicationFunction;
pub use self::polkavm::context::function::runtime::arithmetics::Remainder as PolkaVMRemainderFunction; pub use self::polkavm::context::function::runtime::arithmetics::Remainder as PolkaVMRemainderFunction;
pub use self::polkavm::context::function::runtime::arithmetics::SignedDivision as PolkaVMSignedDivisionFunction; pub use self::polkavm::context::function::runtime::arithmetics::SignedDivision as PolkaVMSignedDivisionFunction;
pub use self::polkavm::context::function::runtime::arithmetics::SignedRemainder as PolkaVMSignedRemainderFunction; pub use self::polkavm::context::function::runtime::arithmetics::SignedRemainder as PolkaVMSignedRemainderFunction;
pub use self::polkavm::context::function::runtime::arithmetics::Subtraction as PolkaVMSubstractionFunction;
pub use self::polkavm::context::function::runtime::bitwise::And as PolkaVMAndFunction;
pub use self::polkavm::context::function::runtime::bitwise::Byte as PolkaVMByteFunction;
pub use self::polkavm::context::function::runtime::bitwise::Or as PolkaVMOrFunction;
pub use self::polkavm::context::function::runtime::bitwise::Sar as PolkaVMSarFunction;
pub use self::polkavm::context::function::runtime::bitwise::Shl as PolkaVMShlFunction;
pub use self::polkavm::context::function::runtime::bitwise::Shr as PolkaVMShrFunction;
pub use self::polkavm::context::function::runtime::bitwise::Xor as PolkaVMXorFunction;
pub use self::polkavm::context::function::runtime::call::Call as PolkaVMCall;
pub use self::polkavm::context::function::runtime::call::CallReentrancyProtector as PolkaVMCallReentrancyProtector;
pub use self::polkavm::context::function::runtime::deploy_code::DeployCode as PolkaVMDeployCodeFunction; pub use self::polkavm::context::function::runtime::deploy_code::DeployCode as PolkaVMDeployCodeFunction;
pub use self::polkavm::context::function::runtime::entry::Entry as PolkaVMEntryFunction; pub use self::polkavm::context::function::runtime::entry::Entry as PolkaVMEntryFunction;
pub use self::polkavm::context::function::runtime::revive::Exit as PolkaVMExitFunction; pub use self::polkavm::context::function::runtime::revive::Exit as PolkaVMExitFunction;
@@ -7,6 +7,141 @@ use crate::polkavm::context::Context;
use crate::polkavm::Dependency; use crate::polkavm::Dependency;
use crate::polkavm::WriteLLVM; use crate::polkavm::WriteLLVM;
/// Implements the ADD operator according to the EVM specification.
pub struct Addition;
impl<D> RuntimeFunction<D> for Addition
where
D: Dependency + Clone,
{
const NAME: &'static str = "__revive_addition";
fn r#type<'ctx>(context: &Context<'ctx, D>) -> inkwell::types::FunctionType<'ctx> {
context.word_type().fn_type(
&[context.word_type().into(), context.word_type().into()],
false,
)
}
fn emit_body<'ctx>(
&self,
context: &mut Context<'ctx, D>,
) -> anyhow::Result<Option<inkwell::values::BasicValueEnum<'ctx>>> {
let operand_1 = Self::paramater(context, 0).into_int_value();
let operand_2 = Self::paramater(context, 1).into_int_value();
Ok(Some(
context
.builder()
.build_int_add(operand_1, operand_2, "ADD")
.map(Into::into)?,
))
}
}
impl<D> WriteLLVM<D> for Addition
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)
}
}
/// Implements the SUB operator according to the EVM specification.
pub struct Subtraction;
impl<D> RuntimeFunction<D> for Subtraction
where
D: Dependency + Clone,
{
const NAME: &'static str = "__revive_subtraction";
fn r#type<'ctx>(context: &Context<'ctx, D>) -> inkwell::types::FunctionType<'ctx> {
context.word_type().fn_type(
&[context.word_type().into(), context.word_type().into()],
false,
)
}
fn emit_body<'ctx>(
&self,
context: &mut Context<'ctx, D>,
) -> anyhow::Result<Option<inkwell::values::BasicValueEnum<'ctx>>> {
let operand_1 = Self::paramater(context, 0).into_int_value();
let operand_2 = Self::paramater(context, 1).into_int_value();
Ok(Some(
context
.builder()
.build_int_sub(operand_1, operand_2, "SUB")
.map(Into::into)?,
))
}
}
impl<D> WriteLLVM<D> for Subtraction
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)
}
}
/// Implements the MUL operator according to the EVM specification.
pub struct Multiplication;
impl<D> RuntimeFunction<D> for Multiplication
where
D: Dependency + Clone,
{
const NAME: &'static str = "__revive_multiplication";
fn r#type<'ctx>(context: &Context<'ctx, D>) -> inkwell::types::FunctionType<'ctx> {
context.word_type().fn_type(
&[context.word_type().into(), context.word_type().into()],
false,
)
}
fn emit_body<'ctx>(
&self,
context: &mut Context<'ctx, D>,
) -> anyhow::Result<Option<inkwell::values::BasicValueEnum<'ctx>>> {
let operand_1 = Self::paramater(context, 0).into_int_value();
let operand_2 = Self::paramater(context, 1).into_int_value();
Ok(Some(
context
.builder()
.build_int_mul(operand_1, operand_2, "MUL")
.map(Into::into)?,
))
}
}
impl<D> WriteLLVM<D> for Multiplication
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)
}
}
/// Implements the division operator according to the EVM specification. /// Implements the division operator according to the EVM specification.
pub struct Division; pub struct Division;
@@ -0,0 +1,494 @@
//! Translates the arithmetic operations.
use inkwell::values::BasicValue;
use crate::polkavm::context::runtime::RuntimeFunction;
use crate::polkavm::context::Context;
use crate::polkavm::Dependency;
use crate::polkavm::WriteLLVM;
/// Implements the OR operator according to the EVM specification.
pub struct Or;
impl<D> RuntimeFunction<D> for Or
where
D: Dependency + Clone,
{
const NAME: &'static str = "__revive_or";
fn r#type<'ctx>(context: &Context<'ctx, D>) -> inkwell::types::FunctionType<'ctx> {
context.word_type().fn_type(
&[context.word_type().into(), context.word_type().into()],
false,
)
}
fn emit_body<'ctx>(
&self,
context: &mut Context<'ctx, D>,
) -> anyhow::Result<Option<inkwell::values::BasicValueEnum<'ctx>>> {
let operand_1 = Self::paramater(context, 0).into_int_value();
let operand_2 = Self::paramater(context, 1).into_int_value();
Ok(Some(
context
.builder()
.build_or(operand_1, operand_2, "OR")
.map(Into::into)?,
))
}
}
impl<D> WriteLLVM<D> for Or
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)
}
}
/// Implements the XOR operator according to the EVM specification.
pub struct Xor;
impl<D> RuntimeFunction<D> for Xor
where
D: Dependency + Clone,
{
const NAME: &'static str = "__revive_xor";
fn r#type<'ctx>(context: &Context<'ctx, D>) -> inkwell::types::FunctionType<'ctx> {
context.word_type().fn_type(
&[context.word_type().into(), context.word_type().into()],
false,
)
}
fn emit_body<'ctx>(
&self,
context: &mut Context<'ctx, D>,
) -> anyhow::Result<Option<inkwell::values::BasicValueEnum<'ctx>>> {
let operand_1 = Self::paramater(context, 0).into_int_value();
let operand_2 = Self::paramater(context, 1).into_int_value();
Ok(Some(
context
.builder()
.build_xor(operand_1, operand_2, "XOR")
.map(Into::into)?,
))
}
}
impl<D> WriteLLVM<D> for Xor
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)
}
}
/// Implements the AND operator according to the EVM specification.
pub struct And;
impl<D> RuntimeFunction<D> for And
where
D: Dependency + Clone,
{
const NAME: &'static str = "__revive_and";
fn r#type<'ctx>(context: &Context<'ctx, D>) -> inkwell::types::FunctionType<'ctx> {
context.word_type().fn_type(
&[context.word_type().into(), context.word_type().into()],
false,
)
}
fn emit_body<'ctx>(
&self,
context: &mut Context<'ctx, D>,
) -> anyhow::Result<Option<inkwell::values::BasicValueEnum<'ctx>>> {
let operand_1 = Self::paramater(context, 0).into_int_value();
let operand_2 = Self::paramater(context, 1).into_int_value();
Ok(Some(
context
.builder()
.build_and(operand_1, operand_2, "AND")
.map(Into::into)?,
))
}
}
impl<D> WriteLLVM<D> for And
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)
}
}
/// Implements the SHL operator according to the EVM specification.
pub struct Shl;
impl<D> RuntimeFunction<D> for Shl
where
D: Dependency + Clone,
{
const NAME: &'static str = "__revive_shl";
fn r#type<'ctx>(context: &Context<'ctx, D>) -> inkwell::types::FunctionType<'ctx> {
context.word_type().fn_type(
&[context.word_type().into(), context.word_type().into()],
false,
)
}
fn emit_body<'ctx>(
&self,
context: &mut Context<'ctx, D>,
) -> anyhow::Result<Option<inkwell::values::BasicValueEnum<'ctx>>> {
let shift = Self::paramater(context, 0).into_int_value();
let value = Self::paramater(context, 1).into_int_value();
let overflow_block = context.append_basic_block("shift_left_overflow");
let non_overflow_block = context.append_basic_block("shift_left_non_overflow");
let join_block = context.append_basic_block("shift_left_join");
let condition_is_overflow = context.builder().build_int_compare(
inkwell::IntPredicate::UGT,
shift,
context.word_const((revive_common::BIT_LENGTH_WORD - 1) as u64),
"shift_left_is_overflow",
)?;
context.build_conditional_branch(
condition_is_overflow,
overflow_block,
non_overflow_block,
)?;
context.set_basic_block(overflow_block);
context.build_unconditional_branch(join_block);
context.set_basic_block(non_overflow_block);
let value =
context
.builder()
.build_left_shift(value, shift, "shift_left_non_overflow_result")?;
context.build_unconditional_branch(join_block);
context.set_basic_block(join_block);
let result = context
.builder()
.build_phi(context.word_type(), "shift_left_value")?;
result.add_incoming(&[
(&value, non_overflow_block),
(&context.word_const(0), overflow_block),
]);
Ok(Some(result.as_basic_value()))
}
}
impl<D> WriteLLVM<D> for Shl
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)
}
}
/// Implements the SHR operator according to the EVM specification.
pub struct Shr;
impl<D> RuntimeFunction<D> for Shr
where
D: Dependency + Clone,
{
const NAME: &'static str = "__revive_shr";
fn r#type<'ctx>(context: &Context<'ctx, D>) -> inkwell::types::FunctionType<'ctx> {
context.word_type().fn_type(
&[context.word_type().into(), context.word_type().into()],
false,
)
}
fn emit_body<'ctx>(
&self,
context: &mut Context<'ctx, D>,
) -> anyhow::Result<Option<inkwell::values::BasicValueEnum<'ctx>>> {
let shift = Self::paramater(context, 0).into_int_value();
let value = Self::paramater(context, 1).into_int_value();
let overflow_block = context.append_basic_block("shift_right_overflow");
let non_overflow_block = context.append_basic_block("shift_right_non_overflow");
let join_block = context.append_basic_block("shift_right_join");
let condition_is_overflow = context.builder().build_int_compare(
inkwell::IntPredicate::UGT,
shift,
context.word_const((revive_common::BIT_LENGTH_WORD - 1) as u64),
"shift_right_is_overflow",
)?;
context.build_conditional_branch(
condition_is_overflow,
overflow_block,
non_overflow_block,
)?;
context.set_basic_block(overflow_block);
context.build_unconditional_branch(join_block);
context.set_basic_block(non_overflow_block);
let value = context.builder().build_right_shift(
value,
shift,
false,
"shift_right_non_overflow_result",
)?;
context.build_unconditional_branch(join_block);
context.set_basic_block(join_block);
let result = context
.builder()
.build_phi(context.word_type(), "shift_right_value")?;
result.add_incoming(&[
(&value, non_overflow_block),
(&context.word_const(0), overflow_block),
]);
Ok(Some(result.as_basic_value()))
}
}
impl<D> WriteLLVM<D> for Shr
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)
}
}
/// Implements the SAR operator according to the EVM specification.
pub struct Sar;
impl<D> RuntimeFunction<D> for Sar
where
D: Dependency + Clone,
{
const NAME: &'static str = "__revive_sar";
fn r#type<'ctx>(context: &Context<'ctx, D>) -> inkwell::types::FunctionType<'ctx> {
context.word_type().fn_type(
&[context.word_type().into(), context.word_type().into()],
false,
)
}
fn emit_body<'ctx>(
&self,
context: &mut Context<'ctx, D>,
) -> anyhow::Result<Option<inkwell::values::BasicValueEnum<'ctx>>> {
let shift = Self::paramater(context, 0).into_int_value();
let value = Self::paramater(context, 1).into_int_value();
let overflow_block = context.append_basic_block("shift_right_arithmetic_overflow");
let overflow_positive_block =
context.append_basic_block("shift_right_arithmetic_overflow_positive");
let overflow_negative_block =
context.append_basic_block("shift_right_arithmetic_overflow_negative");
let non_overflow_block = context.append_basic_block("shift_right_arithmetic_non_overflow");
let join_block = context.append_basic_block("shift_right_arithmetic_join");
let condition_is_overflow = context.builder().build_int_compare(
inkwell::IntPredicate::UGT,
shift,
context.word_const((revive_common::BIT_LENGTH_WORD - 1) as u64),
"shift_right_arithmetic_is_overflow",
)?;
context.build_conditional_branch(
condition_is_overflow,
overflow_block,
non_overflow_block,
)?;
context.set_basic_block(overflow_block);
let sign_bit = context.builder().build_right_shift(
value,
context.word_const((revive_common::BIT_LENGTH_WORD - 1) as u64),
false,
"shift_right_arithmetic_sign_bit",
)?;
let condition_is_negative = context.builder().build_int_truncate_or_bit_cast(
sign_bit,
context.bool_type(),
"shift_right_arithmetic_sign_bit_truncated",
)?;
context.build_conditional_branch(
condition_is_negative,
overflow_negative_block,
overflow_positive_block,
)?;
context.set_basic_block(overflow_positive_block);
context.build_unconditional_branch(join_block);
context.set_basic_block(overflow_negative_block);
context.build_unconditional_branch(join_block);
context.set_basic_block(non_overflow_block);
let value = context.builder().build_right_shift(
value,
shift,
true,
"shift_right_arithmetic_non_overflow_result",
)?;
context.build_unconditional_branch(join_block);
context.set_basic_block(join_block);
let result = context
.builder()
.build_phi(context.word_type(), "shift_arithmetic_right_value")?;
result.add_incoming(&[
(&value, non_overflow_block),
(
&context.word_type().const_all_ones(),
overflow_negative_block,
),
(&context.word_const(0), overflow_positive_block),
]);
Ok(Some(result.as_basic_value()))
}
}
impl<D> WriteLLVM<D> for Sar
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)
}
}
/// Implements the BYTE operator according to the EVM specification.
pub struct Byte;
impl<D> RuntimeFunction<D> for Byte
where
D: Dependency + Clone,
{
const NAME: &'static str = "__revive_byte";
fn r#type<'ctx>(context: &Context<'ctx, D>) -> inkwell::types::FunctionType<'ctx> {
context.word_type().fn_type(
&[context.word_type().into(), context.word_type().into()],
false,
)
}
fn emit_body<'ctx>(
&self,
context: &mut Context<'ctx, D>,
) -> anyhow::Result<Option<inkwell::values::BasicValueEnum<'ctx>>> {
let operand_1 = Self::paramater(context, 0).into_int_value();
let operand_2 = Self::paramater(context, 1).into_int_value();
const MAX_INDEX_BYTES: u64 = 31;
let is_overflow_bit = context.builder().build_int_compare(
inkwell::IntPredicate::ULE,
operand_1,
context.word_const(MAX_INDEX_BYTES),
"is_overflow_bit",
)?;
let is_overflow_byte = context.builder().build_int_z_extend(
is_overflow_bit,
context.byte_type(),
"is_overflow_byte",
)?;
let mask_byte = context.builder().build_int_mul(
context.byte_type().const_all_ones(),
is_overflow_byte,
"mask_byte",
)?;
let mask_byte_word = context.builder().build_int_z_extend(
mask_byte,
context.word_type(),
"mask_byte_word",
)?;
let index_truncated = context.builder().build_int_truncate(
operand_1,
context.byte_type(),
"index_truncated",
)?;
let index_in_bits = context.builder().build_int_mul(
index_truncated,
context
.byte_type()
.const_int(revive_common::BIT_LENGTH_BYTE as u64, false),
"index_in_bits",
)?;
let index_from_most_significant_bit = context.builder().build_int_sub(
context.byte_type().const_int(
MAX_INDEX_BYTES * revive_common::BIT_LENGTH_BYTE as u64,
false,
),
index_in_bits,
"index_from_msb",
)?;
let index_extended = context.builder().build_int_z_extend(
index_from_most_significant_bit,
context.word_type(),
"index",
)?;
let mask = context
.builder()
.build_left_shift(mask_byte_word, index_extended, "mask")?;
let masked_value = context.builder().build_and(operand_2, mask, "masked")?;
let byte =
context
.builder()
.build_right_shift(masked_value, index_extended, false, "byte")?;
Ok(Some(byte.as_basic_value_enum()))
}
}
impl<D> WriteLLVM<D> for Byte
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)
}
}
@@ -0,0 +1,257 @@
//! Translates the arithmetic operations.
use inkwell::values::BasicValue;
use crate::polkavm::context::runtime::RuntimeFunction;
use crate::polkavm::context::Context;
use crate::polkavm::context::Pointer;
use crate::polkavm::Dependency;
use crate::polkavm::WriteLLVM;
const SOLIDITY_TRANSFER_GAS_STIPEND_THRESHOLD: u64 = 2300;
/// The Solidity `address.transfer` and `address.send` call detection heuristic.
///
/// # Why
/// This heuristic is an additional security feature to guard against re-entrancy attacks
/// in case contract authors violate Solidity best practices and use `address.transfer` or
/// `address.send`.
/// While contract authors are supposed to never use `address.transfer` or `address.send`,
/// for a small cost we can be extra defensive about it.
///
/// # How
/// The gas stipend emitted by solc for `transfer` and `send` is not static, thus:
/// - Dynamically allow re-entrancy only for calls considered not transfer or send.
/// - Detected balance transfers will supply 0 deposit limit instead of `u256::MAX`.
///
/// Calls are considered transfer or send if:
/// - (Input length | Output lenght) == 0;
/// - Gas <= 2300;
///
/// # Arguments:
/// - The deposit value pointer.
/// - The gas value.
/// - `input_length | output_length`.
///
///
/// # Returns:
/// The call flags xlen `IntValue`
pub struct CallReentrancyProtector;
impl<D> RuntimeFunction<D> for CallReentrancyProtector
where
D: Dependency + Clone,
{
const NAME: &'static str = "__revive_call_reentrancy_protector";
fn r#type<'ctx>(context: &Context<'ctx, D>) -> inkwell::types::FunctionType<'ctx> {
context.xlen_type().fn_type(
&[
context.llvm().ptr_type(Default::default()).into(),
context.word_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 deposit_pointer = Self::paramater(context, 0).into_pointer_value();
let gas = Self::paramater(context, 1).into_int_value();
let input_length_or_output_length = Self::paramater(context, 2).into_int_value();
// Branch-free SSA implementation: First derive the heuristic boolean (int1) value.
let is_no_input_no_output = context.builder().build_int_compare(
inkwell::IntPredicate::EQ,
context.xlen_type().const_zero(),
input_length_or_output_length,
"is_no_input_no_output",
)?;
let gas_stipend = context
.word_type()
.const_int(SOLIDITY_TRANSFER_GAS_STIPEND_THRESHOLD, false);
let is_gas_stipend_for_transfer_or_send = context.builder().build_int_compare(
inkwell::IntPredicate::ULE,
gas,
gas_stipend,
"is_gas_stipend_for_transfer_or_send",
)?;
let is_balance_transfer = context.builder().build_and(
is_no_input_no_output,
is_gas_stipend_for_transfer_or_send,
"is_balance_transfer",
)?;
let is_regular_call = context
.builder()
.build_not(is_balance_transfer, "is_balance_transfer_inverted")?;
// Call flag: Left shift the heuristic boolean value.
let is_regular_call_xlen = context.builder().build_int_z_extend(
is_regular_call,
context.xlen_type(),
"is_balance_transfer_xlen",
)?;
let call_flags = context.builder().build_left_shift(
is_regular_call_xlen,
context.xlen_type().const_int(3, false),
"flags",
)?;
// Deposit limit value: Sign-extended the heuristic boolean value.
let deposit_limit_value = context.builder().build_int_s_extend(
is_regular_call,
context.word_type(),
"deposit_limit_value",
)?;
context
.builder()
.build_store(deposit_pointer, deposit_limit_value)?;
Ok(Some(call_flags.into()))
}
}
impl<D> WriteLLVM<D> for CallReentrancyProtector
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)
}
}
/// Implements the CALL operator according to the EVM specification.
///
/// # Arguments:
/// - The address value.
/// - The value value.
/// - The input offset.
/// - The input length.
/// - The output offset.
/// - The output length.
/// - The deposit limit pointer.
/// - The call flags.
///
/// # Returns:
/// - The success value (as xlen)
pub struct Call;
impl<D> RuntimeFunction<D> for Call
where
D: Dependency + Clone,
{
const NAME: &'static str = "__revive_call";
fn r#type<'ctx>(context: &Context<'ctx, D>) -> inkwell::types::FunctionType<'ctx> {
context.register_type().fn_type(
&[
context.word_type().into(),
context.word_type().into(),
context.xlen_type().into(),
context.xlen_type().into(),
context.xlen_type().into(),
context.xlen_type().into(),
context.llvm().ptr_type(Default::default()).into(),
context.xlen_type().into(),
],
false,
)
}
fn emit_body<'ctx>(
&self,
context: &mut Context<'ctx, D>,
) -> anyhow::Result<Option<inkwell::values::BasicValueEnum<'ctx>>> {
let address = Self::paramater(context, 0).into_int_value();
let value = Self::paramater(context, 1).into_int_value();
let input_offset = Self::paramater(context, 2).into_int_value();
let input_length = Self::paramater(context, 3).into_int_value();
let output_offset = Self::paramater(context, 4).into_int_value();
let output_length = Self::paramater(context, 5).into_int_value();
let depsit_limit_pointer = Self::paramater(context, 6).into_pointer_value();
let flags = Self::paramater(context, 7).into_int_value();
let address_pointer = context.build_address_argument_store(address)?;
let value_pointer = context.build_alloca_at_entry(context.word_type(), "value_pointer");
context.build_store(value_pointer, value)?;
let input_pointer = context.build_heap_gep(input_offset, input_length)?;
let output_pointer = context.build_heap_gep(output_offset, output_length)?;
let output_length_pointer =
context.build_alloca_at_entry(context.xlen_type(), "output_length");
context.build_store(output_length_pointer, output_length)?;
let flags_and_callee = revive_runtime_api::calling_convention::pack_hi_lo_reg(
context.builder(),
context.llvm(),
flags,
address_pointer.to_int(context),
"address_and_callee",
)?;
let deposit_limit_pointer = Pointer::new(
context.word_type(),
Default::default(),
depsit_limit_pointer,
);
let deposit_and_value = revive_runtime_api::calling_convention::pack_hi_lo_reg(
context.builder(),
context.llvm(),
deposit_limit_pointer.to_int(context),
value_pointer.to_int(context),
"deposit_and_value",
)?;
let input_data = revive_runtime_api::calling_convention::pack_hi_lo_reg(
context.builder(),
context.llvm(),
input_length,
input_pointer.to_int(context),
"input_data",
)?;
let output_data = revive_runtime_api::calling_convention::pack_hi_lo_reg(
context.builder(),
context.llvm(),
output_length_pointer.to_int(context),
output_pointer.to_int(context),
"output_data",
)?;
let name = revive_runtime_api::polkavm_imports::CALL;
let success = context
.build_runtime_call(
name,
&[
flags_and_callee.into(),
context.register_type().const_all_ones().into(),
context.register_type().const_all_ones().into(),
deposit_and_value.into(),
input_data.into(),
output_data.into(),
],
)
.unwrap_or_else(|| panic!("{name} should return a value"))
.into_int_value();
Ok(Some(success.into()))
}
}
impl<D> WriteLLVM<D> for Call
where
D: Dependency + Clone,
{
fn declare(&mut self, context: &mut Context<D>) -> anyhow::Result<()> {
<Self as RuntimeFunction<_>>::declare(self, context)
}
fn into_llvm(self, context: &mut Context<D>) -> anyhow::Result<()> {
<Self as RuntimeFunction<_>>::emit(&self, context)
}
}
@@ -1,6 +1,8 @@
//! The front-end runtime functions. //! The front-end runtime functions.
pub mod arithmetics; pub mod arithmetics;
pub mod bitwise;
pub mod call;
pub mod deploy_code; pub mod deploy_code;
pub mod entry; pub mod entry;
pub mod revive; pub mod revive;
@@ -1,14 +1,15 @@
//! Translates the arithmetic operations. //! Translates the arithmetic operations.
use inkwell::values::BasicValue;
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;
use crate::PolkaVMAdditionFunction;
use crate::PolkaVMDivisionFunction; use crate::PolkaVMDivisionFunction;
use crate::PolkaVMMultiplicationFunction;
use crate::PolkaVMRemainderFunction; use crate::PolkaVMRemainderFunction;
use crate::PolkaVMSignedDivisionFunction; use crate::PolkaVMSignedDivisionFunction;
use crate::PolkaVMSignedRemainderFunction; use crate::PolkaVMSignedRemainderFunction;
use crate::PolkaVMSubstractionFunction;
/// Translates the arithmetic addition. /// Translates the arithmetic addition.
pub fn addition<'ctx, D>( pub fn addition<'ctx, D>(
@@ -19,10 +20,11 @@ pub fn addition<'ctx, D>(
where where
D: Dependency + Clone, D: Dependency + Clone,
{ {
let name = <PolkaVMAdditionFunction as RuntimeFunction<D>>::NAME;
let declaration = <PolkaVMAdditionFunction as RuntimeFunction<D>>::declaration(context);
Ok(context Ok(context
.builder() .build_call(declaration, &[operand_1.into(), operand_2.into()], "SUB")
.build_int_add(operand_1, operand_2, "addition_result")? .unwrap_or_else(|| panic!("revive runtime function {name} should return a value")))
.as_basic_value_enum())
} }
/// Translates the arithmetic subtraction. /// Translates the arithmetic subtraction.
@@ -34,10 +36,11 @@ pub fn subtraction<'ctx, D>(
where where
D: Dependency + Clone, D: Dependency + Clone,
{ {
let name = <PolkaVMSubstractionFunction as RuntimeFunction<D>>::NAME;
let declaration = <PolkaVMSubstractionFunction as RuntimeFunction<D>>::declaration(context);
Ok(context Ok(context
.builder() .build_call(declaration, &[operand_1.into(), operand_2.into()], "SUB")
.build_int_sub(operand_1, operand_2, "subtraction_result")? .unwrap_or_else(|| panic!("revive runtime function {name} should return a value")))
.as_basic_value_enum())
} }
/// Translates the arithmetic multiplication. /// Translates the arithmetic multiplication.
@@ -49,10 +52,11 @@ pub fn multiplication<'ctx, D>(
where where
D: Dependency + Clone, D: Dependency + Clone,
{ {
let name = <PolkaVMMultiplicationFunction as RuntimeFunction<D>>::NAME;
let declaration = <PolkaVMMultiplicationFunction as RuntimeFunction<D>>::declaration(context);
Ok(context Ok(context
.builder() .build_call(declaration, &[operand_1.into(), operand_2.into()], "MUL")
.build_int_mul(operand_1, operand_2, "multiplication_result")? .unwrap_or_else(|| panic!("revive runtime function {name} should return a value")))
.as_basic_value_enum())
} }
/// Translates the arithmetic division. /// Translates the arithmetic division.
+37 -194
View File
@@ -1,9 +1,12 @@
//! Translates the bitwise operations. //! Translates the bitwise operations.
use inkwell::values::BasicValue; use crate::polkavm::context::runtime::RuntimeFunction;
use crate::polkavm::context::Context; use crate::polkavm::context::Context;
use crate::polkavm::Dependency; use crate::polkavm::Dependency;
use crate::{
PolkaVMAndFunction, PolkaVMByteFunction, PolkaVMOrFunction, PolkaVMSarFunction,
PolkaVMShlFunction, PolkaVMShrFunction, PolkaVMXorFunction,
};
/// Translates the bitwise OR. /// Translates the bitwise OR.
pub fn or<'ctx, D>( pub fn or<'ctx, D>(
@@ -14,10 +17,11 @@ pub fn or<'ctx, D>(
where where
D: Dependency + Clone, D: Dependency + Clone,
{ {
let name = <PolkaVMOrFunction as RuntimeFunction<D>>::NAME;
let declaration = <PolkaVMOrFunction as RuntimeFunction<D>>::declaration(context);
Ok(context Ok(context
.builder() .build_call(declaration, &[operand_1.into(), operand_2.into()], "OR")
.build_or(operand_1, operand_2, "or_result")? .unwrap_or_else(|| panic!("revive runtime function {name} should return a value")))
.as_basic_value_enum())
} }
/// Translates the bitwise XOR. /// Translates the bitwise XOR.
@@ -29,10 +33,11 @@ pub fn xor<'ctx, D>(
where where
D: Dependency + Clone, D: Dependency + Clone,
{ {
let name = <PolkaVMXorFunction as RuntimeFunction<D>>::NAME;
let declaration = <PolkaVMXorFunction as RuntimeFunction<D>>::declaration(context);
Ok(context Ok(context
.builder() .build_call(declaration, &[operand_1.into(), operand_2.into()], "XOR")
.build_xor(operand_1, operand_2, "xor_result")? .unwrap_or_else(|| panic!("revive runtime function {name} should return a value")))
.as_basic_value_enum())
} }
/// Translates the bitwise AND. /// Translates the bitwise AND.
@@ -44,10 +49,11 @@ pub fn and<'ctx, D>(
where where
D: Dependency + Clone, D: Dependency + Clone,
{ {
let name = <PolkaVMAndFunction as RuntimeFunction<D>>::NAME;
let declaration = <PolkaVMAndFunction as RuntimeFunction<D>>::declaration(context);
Ok(context Ok(context
.builder() .build_call(declaration, &[operand_1.into(), operand_2.into()], "AND")
.build_and(operand_1, operand_2, "and_result")? .unwrap_or_else(|| panic!("revive runtime function {name} should return a value")))
.as_basic_value_enum())
} }
/// Translates the bitwise shift left. /// Translates the bitwise shift left.
@@ -59,37 +65,11 @@ pub fn shift_left<'ctx, D>(
where where
D: Dependency + Clone, D: Dependency + Clone,
{ {
let overflow_block = context.append_basic_block("shift_left_overflow"); let name = <PolkaVMShlFunction as RuntimeFunction<D>>::NAME;
let non_overflow_block = context.append_basic_block("shift_left_non_overflow"); let declaration = <PolkaVMShlFunction as RuntimeFunction<D>>::declaration(context);
let join_block = context.append_basic_block("shift_left_join"); Ok(context
.build_call(declaration, &[shift.into(), value.into()], "SHL")
let condition_is_overflow = context.builder().build_int_compare( .unwrap_or_else(|| panic!("revive runtime function {name} should return a value")))
inkwell::IntPredicate::UGT,
shift,
context.word_const((revive_common::BIT_LENGTH_WORD - 1) as u64),
"shift_left_is_overflow",
)?;
context.build_conditional_branch(condition_is_overflow, overflow_block, non_overflow_block)?;
context.set_basic_block(overflow_block);
context.build_unconditional_branch(join_block);
context.set_basic_block(non_overflow_block);
let value =
context
.builder()
.build_left_shift(value, shift, "shift_left_non_overflow_result")?;
context.build_unconditional_branch(join_block);
context.set_basic_block(join_block);
let result = context
.builder()
.build_phi(context.word_type(), "shift_left_value")?;
result.add_incoming(&[
(&value, non_overflow_block),
(&context.word_const(0), overflow_block),
]);
Ok(result.as_basic_value())
} }
/// Translates the bitwise shift right. /// Translates the bitwise shift right.
@@ -101,39 +81,11 @@ pub fn shift_right<'ctx, D>(
where where
D: Dependency + Clone, D: Dependency + Clone,
{ {
let overflow_block = context.append_basic_block("shift_right_overflow"); let name = <PolkaVMShrFunction as RuntimeFunction<D>>::NAME;
let non_overflow_block = context.append_basic_block("shift_right_non_overflow"); let declaration = <PolkaVMShrFunction as RuntimeFunction<D>>::declaration(context);
let join_block = context.append_basic_block("shift_right_join"); Ok(context
.build_call(declaration, &[shift.into(), value.into()], "SHR")
let condition_is_overflow = context.builder().build_int_compare( .unwrap_or_else(|| panic!("revive runtime function {name} should return a value")))
inkwell::IntPredicate::UGT,
shift,
context.word_const((revive_common::BIT_LENGTH_WORD - 1) as u64),
"shift_right_is_overflow",
)?;
context.build_conditional_branch(condition_is_overflow, overflow_block, non_overflow_block)?;
context.set_basic_block(overflow_block);
context.build_unconditional_branch(join_block);
context.set_basic_block(non_overflow_block);
let value = context.builder().build_right_shift(
value,
shift,
false,
"shift_right_non_overflow_result",
)?;
context.build_unconditional_branch(join_block);
context.set_basic_block(join_block);
let result = context
.builder()
.build_phi(context.word_type(), "shift_right_value")?;
result.add_incoming(&[
(&value, non_overflow_block),
(&context.word_const(0), overflow_block),
]);
Ok(result.as_basic_value())
} }
/// Translates the arithmetic bitwise shift right. /// Translates the arithmetic bitwise shift right.
@@ -145,68 +97,11 @@ pub fn shift_right_arithmetic<'ctx, D>(
where where
D: Dependency + Clone, D: Dependency + Clone,
{ {
let overflow_block = context.append_basic_block("shift_right_arithmetic_overflow"); let name = <PolkaVMSarFunction as RuntimeFunction<D>>::NAME;
let overflow_positive_block = let declaration = <PolkaVMSarFunction as RuntimeFunction<D>>::declaration(context);
context.append_basic_block("shift_right_arithmetic_overflow_positive"); Ok(context
let overflow_negative_block = .build_call(declaration, &[shift.into(), value.into()], "SHR")
context.append_basic_block("shift_right_arithmetic_overflow_negative"); .unwrap_or_else(|| panic!("revive runtime function {name} should return a value")))
let non_overflow_block = context.append_basic_block("shift_right_arithmetic_non_overflow");
let join_block = context.append_basic_block("shift_right_arithmetic_join");
let condition_is_overflow = context.builder().build_int_compare(
inkwell::IntPredicate::UGT,
shift,
context.word_const((revive_common::BIT_LENGTH_WORD - 1) as u64),
"shift_right_arithmetic_is_overflow",
)?;
context.build_conditional_branch(condition_is_overflow, overflow_block, non_overflow_block)?;
context.set_basic_block(overflow_block);
let sign_bit = context.builder().build_right_shift(
value,
context.word_const((revive_common::BIT_LENGTH_WORD - 1) as u64),
false,
"shift_right_arithmetic_sign_bit",
)?;
let condition_is_negative = context.builder().build_int_truncate_or_bit_cast(
sign_bit,
context.bool_type(),
"shift_right_arithmetic_sign_bit_truncated",
)?;
context.build_conditional_branch(
condition_is_negative,
overflow_negative_block,
overflow_positive_block,
)?;
context.set_basic_block(overflow_positive_block);
context.build_unconditional_branch(join_block);
context.set_basic_block(overflow_negative_block);
context.build_unconditional_branch(join_block);
context.set_basic_block(non_overflow_block);
let value = context.builder().build_right_shift(
value,
shift,
true,
"shift_right_arithmetic_non_overflow_result",
)?;
context.build_unconditional_branch(join_block);
context.set_basic_block(join_block);
let result = context
.builder()
.build_phi(context.word_type(), "shift_arithmetic_right_value")?;
result.add_incoming(&[
(&value, non_overflow_block),
(
&context.word_type().const_all_ones(),
overflow_negative_block,
),
(&context.word_const(0), overflow_block),
]);
Ok(result.as_basic_value())
} }
/// Translates the `byte` instruction, extracting the byte of `operand_2` /// Translates the `byte` instruction, extracting the byte of `operand_2`
@@ -225,61 +120,9 @@ pub fn byte<'ctx, D>(
where where
D: Dependency + Clone, D: Dependency + Clone,
{ {
const MAX_INDEX_BYTES: u64 = 31; let name = <PolkaVMByteFunction as RuntimeFunction<D>>::NAME;
let declaration = <PolkaVMByteFunction as RuntimeFunction<D>>::declaration(context);
let is_overflow_bit = context.builder().build_int_compare( Ok(context
inkwell::IntPredicate::ULE, .build_call(declaration, &[operand_1.into(), operand_2.into()], "BYTE")
operand_1, .unwrap_or_else(|| panic!("revive runtime function {name} should return a value")))
context.word_const(MAX_INDEX_BYTES),
"is_overflow_bit",
)?;
let is_overflow_byte = context.builder().build_int_z_extend(
is_overflow_bit,
context.byte_type(),
"is_overflow_byte",
)?;
let mask_byte = context.builder().build_int_mul(
context.byte_type().const_all_ones(),
is_overflow_byte,
"mask_byte",
)?;
let mask_byte_word =
context
.builder()
.build_int_z_extend(mask_byte, context.word_type(), "mask_byte_word")?;
let index_truncated =
context
.builder()
.build_int_truncate(operand_1, context.byte_type(), "index_truncated")?;
let index_in_bits = context.builder().build_int_mul(
index_truncated,
context
.byte_type()
.const_int(revive_common::BIT_LENGTH_BYTE as u64, false),
"index_in_bits",
)?;
let index_from_most_significant_bit = context.builder().build_int_sub(
context.byte_type().const_int(
MAX_INDEX_BYTES * revive_common::BIT_LENGTH_BYTE as u64,
false,
),
index_in_bits,
"index_from_msb",
)?;
let index_extended = context.builder().build_int_z_extend(
index_from_most_significant_bit,
context.word_type(),
"index",
)?;
let mask = context
.builder()
.build_left_shift(mask_byte_word, index_extended, "mask")?;
let masked_value = context.builder().build_and(operand_2, mask, "masked")?;
let byte = context
.builder()
.build_right_shift(masked_value, index_extended, false, "byte")?;
Ok(byte.as_basic_value_enum())
} }
+45 -135
View File
@@ -3,12 +3,13 @@
use inkwell::values::BasicValue; use inkwell::values::BasicValue;
use crate::polkavm::context::argument::Argument; use crate::polkavm::context::argument::Argument;
use crate::polkavm::context::runtime::RuntimeFunction;
use crate::polkavm::context::Context; use crate::polkavm::context::Context;
use crate::polkavm::Dependency; use crate::polkavm::Dependency;
use crate::{PolkaVMCall, PolkaVMCallReentrancyProtector};
const STATIC_CALL_FLAG: u32 = 0b0001_0000; const STATIC_CALL_FLAG: u32 = 0b0001_0000;
const REENTRANT_CALL_FLAG: u32 = 0b0000_1000; const REENTRANT_CALL_FLAG: u32 = 0b0000_1000;
const SOLIDITY_TRANSFER_GAS_STIPEND_THRESHOLD: u64 = 2300;
/// Translates a contract call. /// Translates a contract call.
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
@@ -27,79 +28,45 @@ pub fn call<'ctx, D>(
where where
D: Dependency + Clone, D: Dependency + Clone,
{ {
let address_pointer = context.build_address_argument_store(address)?;
let value = value.unwrap_or_else(|| context.word_const(0));
let value_pointer = context.build_alloca_at_entry(context.word_type(), "value_pointer");
context.build_store(value_pointer, value)?;
let input_offset = context.safe_truncate_int_to_xlen(input_offset)?; let input_offset = context.safe_truncate_int_to_xlen(input_offset)?;
let input_length = context.safe_truncate_int_to_xlen(input_length)?;
let output_offset = context.safe_truncate_int_to_xlen(output_offset)?; let output_offset = context.safe_truncate_int_to_xlen(output_offset)?;
let input_length = context.safe_truncate_int_to_xlen(input_length)?;
let output_length = context.safe_truncate_int_to_xlen(output_length)?; let output_length = context.safe_truncate_int_to_xlen(output_length)?;
let input_pointer = context.build_heap_gep(input_offset, input_length)?; let deposit_limit_pointer =
let output_pointer = context.build_heap_gep(output_offset, output_length)?; context.build_alloca_at_entry(context.word_type(), "deposit_pointer");
let flags = if static_call {
let output_length_pointer = context.build_alloca_at_entry(context.xlen_type(), "output_length");
context.build_store(output_length_pointer, output_length)?;
let (flags, deposit_limit_value) = if static_call {
let flags = REENTRANT_CALL_FLAG | STATIC_CALL_FLAG; let flags = REENTRANT_CALL_FLAG | STATIC_CALL_FLAG;
( context.build_store(deposit_limit_pointer, context.word_type().const_zero())?;
context.xlen_type().const_int(flags as u64, false), context.xlen_type().const_int(flags as u64, false)
context.word_type().const_zero(),
)
} else { } else {
call_reentrancy_heuristic(context, gas, input_length, output_length)? call_reentrancy_heuristic(
context,
deposit_limit_pointer.value,
gas,
input_length,
output_length,
)?
}; };
let deposit_pointer = context.build_alloca_at_entry(context.word_type(), "deposit_pointer"); let value = value.unwrap_or_else(|| context.word_const(0));
context.build_store(deposit_pointer, deposit_limit_value)?;
let flags_and_callee = revive_runtime_api::calling_convention::pack_hi_lo_reg( let name = <PolkaVMCall as RuntimeFunction<D>>::NAME;
context.builder(), let declaration = <PolkaVMCall as RuntimeFunction<D>>::declaration(context);
context.llvm(), let arguments = &[
flags, address.into(),
address_pointer.to_int(context), value.into(),
"address_and_callee", input_offset.into(),
)?; input_length.into(),
let deposit_and_value = revive_runtime_api::calling_convention::pack_hi_lo_reg( output_offset.into(),
context.builder(), output_length.into(),
context.llvm(), deposit_limit_pointer.value.into(),
deposit_pointer.to_int(context), flags.into(),
value_pointer.to_int(context), ];
"deposit_and_value",
)?;
let input_data = revive_runtime_api::calling_convention::pack_hi_lo_reg(
context.builder(),
context.llvm(),
input_length,
input_pointer.to_int(context),
"input_data",
)?;
let output_data = revive_runtime_api::calling_convention::pack_hi_lo_reg(
context.builder(),
context.llvm(),
output_length_pointer.to_int(context),
output_pointer.to_int(context),
"output_data",
)?;
let name = revive_runtime_api::polkavm_imports::CALL;
let success = context let success = context
.build_runtime_call( .build_call(declaration, arguments, "call")
name, .unwrap_or_else(|| panic!("revive runtime function {name} should return a value"))
&[
flags_and_callee.into(),
context.register_type().const_all_ones().into(),
context.register_type().const_all_ones().into(),
deposit_and_value.into(),
input_data.into(),
output_data.into(),
],
)
.unwrap_or_else(|| panic!("{name} should return a value"))
.into_int_value(); .into_int_value();
let is_success = context.builder().build_int_compare( let is_success = context.builder().build_int_compare(
@@ -216,85 +183,28 @@ where
.as_basic_value_enum()) .as_basic_value_enum())
} }
/// The Solidity `address.transfer` and `address.send` call detection heuristic.
///
/// # Why
/// This heuristic is an additional security feature to guard against re-entrancy attacks
/// in case contract authors violate Solidity best practices and use `address.transfer` or
/// `address.send`.
/// While contract authors are supposed to never use `address.transfer` or `address.send`,
/// for a small cost we can be extra defensive about it.
///
/// # How
/// The gas stipend emitted by solc for `transfer` and `send` is not static, thus:
/// - Dynamically allow re-entrancy only for calls considered not transfer or send.
/// - Detected balance transfers will supply 0 deposit limit instead of `u256::MAX`.
///
/// Calls are considered transfer or send if:
/// - (Input length | Output lenght) == 0;
/// - Gas <= 2300;
///
/// # Returns
/// The call flags xlen `IntValue` and the deposit limit word `IntValue`.
fn call_reentrancy_heuristic<'ctx, D>( fn call_reentrancy_heuristic<'ctx, D>(
context: &mut Context<'ctx, D>, context: &mut Context<'ctx, D>,
deposit_limit_pointer: inkwell::values::PointerValue<'ctx>,
gas: inkwell::values::IntValue<'ctx>, gas: inkwell::values::IntValue<'ctx>,
input_length: inkwell::values::IntValue<'ctx>, input_length: inkwell::values::IntValue<'ctx>,
output_length: inkwell::values::IntValue<'ctx>, output_length: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<( ) -> anyhow::Result<inkwell::values::IntValue<'ctx>>
inkwell::values::IntValue<'ctx>,
inkwell::values::IntValue<'ctx>,
)>
where where
D: Dependency + Clone, D: Dependency + Clone,
{ {
// Branch-free SSA implementation: First derive the heuristic boolean (int1) value. let name = <PolkaVMCallReentrancyProtector as RuntimeFunction<D>>::NAME;
let input_length_or_output_length = let declaration = <PolkaVMCallReentrancyProtector as RuntimeFunction<D>>::declaration(context);
let arguments = &[
deposit_limit_pointer.into(),
gas.into(),
context context
.builder() .builder()
.build_or(input_length, output_length, "input_length_or_output_length")?; .build_or(input_length, output_length, "input_length_or_output_length")?
let is_no_input_no_output = context.builder().build_int_compare( .into(),
inkwell::IntPredicate::EQ, ];
context.xlen_type().const_zero(), Ok(context
input_length_or_output_length, .build_call(declaration, arguments, "call_flags")
"is_no_input_no_output", .unwrap_or_else(|| panic!("revive runtime function {name} should return a value"))
)?; .into_int_value())
let gas_stipend = context
.word_type()
.const_int(SOLIDITY_TRANSFER_GAS_STIPEND_THRESHOLD, false);
let is_gas_stipend_for_transfer_or_send = context.builder().build_int_compare(
inkwell::IntPredicate::ULE,
gas,
gas_stipend,
"is_gas_stipend_for_transfer_or_send",
)?;
let is_balance_transfer = context.builder().build_and(
is_no_input_no_output,
is_gas_stipend_for_transfer_or_send,
"is_balance_transfer",
)?;
let is_regular_call = context
.builder()
.build_not(is_balance_transfer, "is_balance_transfer_inverted")?;
// Call flag: Left shift the heuristic boolean value.
let is_regular_call_xlen = context.builder().build_int_z_extend(
is_regular_call,
context.xlen_type(),
"is_balance_transfer_xlen",
)?;
let call_flags = context.builder().build_left_shift(
is_regular_call_xlen,
context.xlen_type().const_int(3, false),
"flags",
)?;
// Deposit limit value: Sign-extended the heuristic boolean value.
let deposit_limit_value = context.builder().build_int_s_extend(
is_regular_call,
context.word_type(),
"deposit_limit_value",
)?;
Ok((call_flags, deposit_limit_value))
} }
+1 -1
View File
@@ -37,7 +37,7 @@ pub fn build_assembly_text(
let mut disassembled_code = Vec::new(); let mut disassembled_code = Vec::new();
disassembler disassembler
.disassemble_into(&mut disassembled_code) .disassemble_into(&mut disassembled_code)
.with_context(|| format!("Failed to disassemble contract: {}", contract_path))?; .with_context(|| format!("Failed to disassemble contract: {contract_path}"))?;
let assembly_text = String::from_utf8(disassembled_code).with_context(|| { let assembly_text = String::from_utf8(disassembled_code).with_context(|| {
format!("Failed to convert disassembled code to string for contract: {contract_path}") format!("Failed to convert disassembled code to string for contract: {contract_path}")
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "resolc" name = "resolc"
version = "0.2.0" version = "0.3.0"
license.workspace = true license.workspace = true
edition.workspace = true edition.workspace = true
repository.workspace = true repository.workspace = true
+2 -2
View File
@@ -155,8 +155,8 @@ impl Project {
.iter() .iter()
.flat_map(|(file, names)| { .flat_map(|(file, names)| {
names names
.iter() .keys()
.map(|(name, _address)| format!("{file}:{name}")) .map(|name| format!("{file}:{name}"))
.collect::<HashSet<String>>() .collect::<HashSet<String>>()
}) })
.collect::<HashSet<String>>(); .collect::<HashSet<String>>();
+1 -3
View File
@@ -240,9 +240,7 @@ fn main_inner() -> anyhow::Result<()> {
writeln!( writeln!(
std::io::stdout(), std::io::stdout(),
"Contract `{}` assembly:\n\n{}", "Contract `{path}` assembly:\n\n{assembly_text}"
path,
assembly_text
)?; )?;
} }
if arguments.output_binary { if arguments.output_binary {
+1 -1
View File
@@ -361,7 +361,7 @@ fn compile_evm(
.expect("source should compile"); .expect("source should compile");
let object = &contracts let object = &contracts
.get(contract_name) .get(contract_name)
.unwrap_or_else(|| panic!("contract '{}' didn't produce bin-runtime", contract_name)); .unwrap_or_else(|| panic!("contract '{contract_name}' didn't produce bin-runtime"));
let code = if runtime { let code = if runtime {
object.1.object.as_str() object.1.object.as_str()
} else { } else {
@@ -0,0 +1,22 @@
object "Test" {
code {
function allocate(size) -> ptr {
ptr := mload(0x40)
if iszero(ptr) { ptr := 0x60 }
mstore(0x40, add(ptr, size))
}
let size := datasize("Test_deployed")
let offset := allocate(size)
datacopy(offset, dataoffset("Test_deployed"), size)
return(offset, size)
}
object "Test_deployed" {
code {
{
let test:=0x5
mstore(2,signextend(0x8,0x0))
mstore(8,lt(0xc,test))
}
return(0, 65536)
}}}
@@ -15,6 +15,11 @@ const pathToBasicYulContract = path.join(
'yul', 'yul',
contractYulFilename contractYulFilename
) )
const pathToMemsetYulContract = path.join(
pathToContracts,
'yul',
'memset.yul'
)
const pathToBasicSolContract = path.join( const pathToBasicSolContract = path.join(
pathToContracts, pathToContracts,
'solidity', 'solidity',
@@ -42,6 +47,7 @@ export const paths = {
pathToContracts: pathToContracts, pathToContracts: pathToContracts,
pathToBasicSolContract: pathToBasicSolContract, pathToBasicSolContract: pathToBasicSolContract,
pathToBasicYulContract: pathToBasicYulContract, pathToBasicYulContract: pathToBasicYulContract,
pathToMemsetYulContract: pathToMemsetYulContract,
pathToSolBinOutputFile: pathToSolBinOutputFile, pathToSolBinOutputFile: pathToSolBinOutputFile,
pathToSolAsmOutputFile: pathToSolAsmOutputFile, pathToSolAsmOutputFile: pathToSolAsmOutputFile,
} }
@@ -0,0 +1,18 @@
import { executeCommand } from '../src/helper'
import { paths } from '../src/entities'
describe('tests for the memset builtin to be present', () => {
// -O3 is required to reproduce.
const command = `resolc ${paths.pathToMemsetYulContract} --yul -O3`
const result = executeCommand(command)
it('Valid command exit code = 0', () => {
expect(result.exitCode).toBe(0)
})
it('--yul output is presented', () => {
expect(result.output).toMatch(/(Compiler run successful)/i)
expect(result.output).toMatch(/(No output requested)/i)
})
})
+2 -2
View File
@@ -121,10 +121,10 @@ fn optimizer() {
assert!( assert!(
size_when_optimized_for_cycles < size_when_unoptimized, size_when_optimized_for_cycles < size_when_unoptimized,
"Expected the cycles-optimized bytecode to be smaller than the unoptimized. Optimized: {}B, Unoptimized: {}B", size_when_optimized_for_cycles, size_when_unoptimized, "Expected the cycles-optimized bytecode to be smaller than the unoptimized. Optimized: {size_when_optimized_for_cycles}B, Unoptimized: {size_when_unoptimized}B",
); );
assert!( assert!(
size_when_optimized_for_size < size_when_unoptimized, size_when_optimized_for_size < size_when_unoptimized,
"Expected the size-optimized bytecode to be smaller than the unoptimized. Optimized: {}B, Unoptimized: {}B", size_when_optimized_for_size, size_when_unoptimized, "Expected the size-optimized bytecode to be smaller than the unoptimized. Optimized: {size_when_optimized_for_size}B, Unoptimized: {size_when_unoptimized}B",
); );
} }
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "revive-runtime-api" name = "revive-runtime-api"
version.workspace = true version = "0.2.0"
license.workspace = true license.workspace = true
edition.workspace = true edition.workspace = true
repository.workspace = true repository.workspace = true
+2 -3
View File
@@ -37,12 +37,11 @@ fn compile(source_path: &str, bitcode_path: &str) {
source_path, source_path,
]) ])
.output() .output()
.unwrap_or_else(|error| panic!("failed to execute clang: {}", error)); .unwrap_or_else(|error| panic!("failed to execute clang: {error}"));
assert!( assert!(
output.status.success(), output.status.success(),
"failed to compile the PolkaVM C API: {:?}", "failed to compile the PolkaVM C API: {output:?}"
output
); );
} }
+7
View File
@@ -30,6 +30,13 @@ void * memmove(void *dst, const void *src, size_t n) {
return dst; return dst;
} }
void * memset(void *b, int c, size_t len) {
uint8_t *dest = b;
uint8_t val = (uint8_t)c;
while (len-- > 0) *dest++ = val;
return b;
}
// Imports // Imports
POLKAVM_IMPORT(void, address, uint32_t) POLKAVM_IMPORT(void, address, uint32_t)
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "revive-stdlib" name = "revive-stdlib"
version.workspace = true version = "0.1.1"
license.workspace = true license.workspace = true
edition.workspace = true edition.workspace = true
repository.workspace = true repository.workspace = true
+2 -3
View File
@@ -17,12 +17,11 @@ fn main() {
"stdlib.ll", "stdlib.ll",
]) ])
.output() .output()
.unwrap_or_else(|error| panic!("failed to execute llvm-as: {}", error)); .unwrap_or_else(|error| panic!("failed to execute llvm-as: {error}"));
assert!( assert!(
output.status.success(), output.status.success(),
"failed to assemble the stdlib: {:?}", "failed to assemble the stdlib: {output:?}"
output
); );
let bitcode = fs::read(bitcode_path).expect("bitcode should have been built"); let bitcode = fs::read(bitcode_path).expect("bitcode should have been built");
+1 -1
View File
@@ -1,7 +1,7 @@
[package] [package]
name = "revive-yul" name = "revive-yul"
description = "The revive YUL parser library." description = "The revive YUL parser library."
version = "0.2.0" version = "0.2.1"
authors.workspace = true authors.workspace = true
license.workspace = true license.workspace = true
edition.workspace = true edition.workspace = true
@@ -167,7 +167,7 @@ impl Literal {
unicode_char.encode_utf8(&mut unicode_bytes); unicode_char.encode_utf8(&mut unicode_bytes);
for byte in unicode_bytes.into_iter() { for byte in unicode_bytes.into_iter() {
hex_string.push_str(format!("{:02x}", byte).as_str()); hex_string.push_str(format!("{byte:02x}").as_str());
} }
index += 5; index += 5;
} else if string[index..].starts_with('t') { } else if string[index..].starts_with('t') {
+28
View File
@@ -204,11 +204,25 @@ where
revive_llvm_context::PolkaVMEventLogFunction::<3>.declare(context)?; revive_llvm_context::PolkaVMEventLogFunction::<3>.declare(context)?;
revive_llvm_context::PolkaVMEventLogFunction::<4>.declare(context)?; revive_llvm_context::PolkaVMEventLogFunction::<4>.declare(context)?;
revive_llvm_context::PolkaVMAdditionFunction.declare(context)?;
revive_llvm_context::PolkaVMSubstractionFunction.declare(context)?;
revive_llvm_context::PolkaVMMultiplicationFunction.declare(context)?;
revive_llvm_context::PolkaVMDivisionFunction.declare(context)?; revive_llvm_context::PolkaVMDivisionFunction.declare(context)?;
revive_llvm_context::PolkaVMSignedDivisionFunction.declare(context)?; revive_llvm_context::PolkaVMSignedDivisionFunction.declare(context)?;
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::PolkaVMOrFunction.declare(context)?;
revive_llvm_context::PolkaVMXorFunction.declare(context)?;
revive_llvm_context::PolkaVMAndFunction.declare(context)?;
revive_llvm_context::PolkaVMShlFunction.declare(context)?;
revive_llvm_context::PolkaVMShrFunction.declare(context)?;
revive_llvm_context::PolkaVMSarFunction.declare(context)?;
revive_llvm_context::PolkaVMByteFunction.declare(context)?;
revive_llvm_context::PolkaVMCall.declare(context)?;
revive_llvm_context::PolkaVMCallReentrancyProtector.declare(context)?;
revive_llvm_context::PolkaVMSbrkFunction.declare(context)?; revive_llvm_context::PolkaVMSbrkFunction.declare(context)?;
let mut entry = revive_llvm_context::PolkaVMEntryFunction::default(); let mut entry = revive_llvm_context::PolkaVMEntryFunction::default();
@@ -258,11 +272,25 @@ where
revive_llvm_context::PolkaVMEventLogFunction::<3>.into_llvm(context)?; revive_llvm_context::PolkaVMEventLogFunction::<3>.into_llvm(context)?;
revive_llvm_context::PolkaVMEventLogFunction::<4>.into_llvm(context)?; revive_llvm_context::PolkaVMEventLogFunction::<4>.into_llvm(context)?;
revive_llvm_context::PolkaVMAdditionFunction.into_llvm(context)?;
revive_llvm_context::PolkaVMSubstractionFunction.into_llvm(context)?;
revive_llvm_context::PolkaVMMultiplicationFunction.into_llvm(context)?;
revive_llvm_context::PolkaVMDivisionFunction.into_llvm(context)?; revive_llvm_context::PolkaVMDivisionFunction.into_llvm(context)?;
revive_llvm_context::PolkaVMSignedDivisionFunction.into_llvm(context)?; revive_llvm_context::PolkaVMSignedDivisionFunction.into_llvm(context)?;
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::PolkaVMOrFunction.into_llvm(context)?;
revive_llvm_context::PolkaVMXorFunction.into_llvm(context)?;
revive_llvm_context::PolkaVMAndFunction.into_llvm(context)?;
revive_llvm_context::PolkaVMShlFunction.into_llvm(context)?;
revive_llvm_context::PolkaVMShrFunction.into_llvm(context)?;
revive_llvm_context::PolkaVMSarFunction.into_llvm(context)?;
revive_llvm_context::PolkaVMByteFunction.into_llvm(context)?;
revive_llvm_context::PolkaVMCall.into_llvm(context)?;
revive_llvm_context::PolkaVMCallReentrancyProtector.into_llvm(context)?;
revive_llvm_context::PolkaVMSbrkFunction.into_llvm(context)?; revive_llvm_context::PolkaVMSbrkFunction.into_llvm(context)?;
Ok(()) Ok(())
+36 -35
View File
@@ -1,38 +1,39 @@
{ {
"name": "@parity/resolc", "name": "@parity/resolc",
"license": "Apache-2.0", "license": "Apache-2.0",
"version": "0.2.0", "version": "0.3.0",
"author": "Parity <admin@parity.io> (https://parity.io)", "author": "Parity <admin@parity.io> (https://parity.io)",
"module": "index.ts", "module": "index.ts",
"types": "./dist/index.d.ts", "types": "./dist/index.d.ts",
"main": "./dist/index.js", "main": "./dist/index.js",
"bin": { "bin": {
"resolc": "./dist/bin.js" "resolc": "./dist/bin.js"
}, },
"exports": { "exports": {
".": { ".": {
"import": "./dist/index.js", "import": "./dist/index.js",
"require": "./dist/index.js", "require": "./dist/index.js",
"types": "./dist/index.d.ts" "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",
"resolve-pkg": "^2.0.0",
"solc": ">=0.8.0 <=0.8.30"
} }
},
"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",
"@redstone-finance/evm-connector": "^0.8.0",
"globals": "^15.12.0",
"typescript": "^5.6.3"
},
"dependencies": {
"@types/node": "^22.9.0",
"commander": "^13.1.0",
"package-json": "^10.0.1",
"resolve-pkg": "^2.0.0",
"solc": ">=0.8.0 <=0.8.30"
}
} }
+55 -10
View File
@@ -6,6 +6,7 @@ import * as os from 'os'
import * as path from 'path' import * as path from 'path'
import * as resolc from '.' import * as resolc from '.'
import { SolcInput } from '.' import { SolcInput } from '.'
import { execSync } from 'child_process'
async function main() { 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 // hold on to any exception handlers that existed prior to this script running, we'll be adding them back at the end
@@ -17,11 +18,15 @@ async function main() {
const program = new commander.Command() const program = new commander.Command()
program.name('solcjs') program.name('resolcjs')
program.version(resolc.version()) program.version(resolc.version())
program program
.option('--bin', 'Binary of the contracts in hex.') .option('--bin', 'Binary of the contracts in hex.')
.option('--abi', 'ABI of the contracts.') .option('--abi', 'ABI of the contracts.')
.option(
'--diff-stats',
'Print statistics about Resolc vs Solc compilation.'
)
.option( .option(
'--base-path <path>', '--base-path <path>',
'Root of the project source tree. ' + 'Root of the project source tree. ' +
@@ -49,8 +54,20 @@ async function main() {
outputDir?: string outputDir?: string
prettyJson: boolean prettyJson: boolean
basePath?: string basePath?: string
diffStats: boolean
includePath?: string[] includePath?: string[]
}>() }>()
// when using --stats option, we want to run solc as well to compare outputs size
if (options.diffStats) {
const args = process.argv.filter((arg) => !arg.startsWith('--diff-stats'))
try {
execSync(`npx --yes solc@latest ${args.slice(2).join(' ')}`)
} catch (err) {
abort(`Failed to run solc: ${err}`)
}
}
const files: string[] = program.args const files: string[] = program.args
const destination = options.outputDir ?? '.' const destination = options.outputDir ?? '.'
@@ -137,7 +154,7 @@ async function main() {
if (!output) { if (!output) {
abort('No output from compiler') abort('No output from compiler')
} else if (output.errors) { } else if (output.errors && !options.diffStats) {
for (const error in output.errors) { for (const error in output.errors) {
const message = output.errors[error] const message = output.errors[error]
if (message.severity === 'warning') { if (message.severity === 'warning') {
@@ -160,19 +177,41 @@ async function main() {
}) })
} }
const contractStats = []
for (const fileName in output.contracts) { for (const fileName in output.contracts) {
for (const contractName in output.contracts[fileName]) { for (const contractName in output.contracts[fileName]) {
let contractFileName = fileName + ':' + contractName let contractFileName = fileName + ':' + contractName
contractFileName = contractFileName.replace(/[:./\\]/g, '_') contractFileName = contractFileName.replace(/[:./\\]/g, '_')
let polkavmSize = 0
if (options.bin) { let binSize = 0
writeFile( if (
contractFileName + '.polkavm', options.bin &&
Buffer.from( output.contracts?.[fileName]?.[contractName]?.evm?.bytecode?.object
output.contracts[fileName][contractName].evm.bytecode.object, ) {
'hex' const pvmData = Buffer.from(
) output.contracts[fileName][contractName].evm.bytecode.object,
'hex'
) )
writeFile(contractFileName + '.polkavm', pvmData)
polkavmSize = pvmData.length
const binOutPath = path.join(destination, `${contractFileName}.bin`)
if (fs.existsSync(binOutPath)) {
try {
binSize = fs.statSync(binOutPath).size || 0
} catch {}
}
contractStats.push({
file: fileName,
contract: contractName,
['resolc (kB)']: Number((polkavmSize / 1024).toFixed(2)),
['solc (kB)']: Number((binSize / 1024).toFixed(2)),
['diff (%)']:
binSize > 0
? Number(((polkavmSize / binSize - 1) * 100).toFixed(2))
: Number.NaN,
})
} }
if (options.abi) { if (options.abi) {
@@ -184,6 +223,12 @@ async function main() {
} }
} }
if (options.diffStats && contractStats.length > 0) {
console.table(
contractStats.sort((a, b) => b['resolc (kB)'] - a['resolc (kB)'])
)
}
// Put back original exception handlers. // Put back original exception handlers.
originalUncaughtExceptionListeners.forEach(function (listener) { originalUncaughtExceptionListeners.forEach(function (listener) {
process.addListener('uncaughtException', listener) process.addListener('uncaughtException', listener)
+19 -6
View File
@@ -68,6 +68,16 @@ test('resolve import', () => {
file: './fixtures/storage.sol', file: './fixtures/storage.sol',
expected: resolve('fixtures/storage.sol'), expected: resolve('fixtures/storage.sol'),
}, },
// package with exports
{
file: '@redstone-finance/evm-connector/contracts/data-services/PrimaryProdDataServiceConsumerBase.sol',
expected: resolve(
__dirname,
'../../..',
'node_modules/@redstone-finance/evm-connector/contracts/data-services/PrimaryProdDataServiceConsumerBase.sol'
),
},
// scopped module with version // scopped module with version
{ {
file: '@openzeppelin/contracts@5.1.0/token/ERC20/ERC20.sol', file: '@openzeppelin/contracts@5.1.0/token/ERC20/ERC20.sol',
@@ -100,16 +110,19 @@ test('resolve import', () => {
] ]
for (const { file, expected } of cases) { for (const { file, expected } of cases) {
let resolved
try { try {
const resolved = tryResolveImport(file) resolved = tryResolveImport(file)
assert(
resolved === expected,
`\nGot:\n${resolved}\nExpected:\n${expected}`
)
} catch (error) { } catch (error) {
assert( assert(
String(error) == expected, String(error) == expected,
`\nGot:\n${String(error)}\nExpected:\n${expected}` `\nExpected:\n${expected}\nGot:\n${String(error)}\n`
)
}
if (resolved) {
assert(
resolved === expected,
`\nExpected:\n${expected}\nGot:\n${resolved}`
) )
} }
} }
+23 -7
View File
@@ -50,11 +50,7 @@ export function resolveInputs(sources: SolcInput): SolcInput {
language: 'Solidity', language: 'Solidity',
sources, sources,
settings: { settings: {
outputSelection: { outputSelection: {}, // no outputs requested, only resolves imports
'*': {
'*': ['evm.bytecode.object'],
},
},
}, },
} }
@@ -106,7 +102,7 @@ export async function compile(
enabled: true, enabled: true,
runs: 200, runs: 200,
}, },
bin, bin = process.env.RESOLC_BIN,
} = option } = option
const input = JSON.stringify({ const input = JSON.stringify({
@@ -129,6 +125,26 @@ export async function compile(
return resolc(input) return resolc(input)
} }
/**
* resolve the package root
* use resolve-pkg to find the package root, and fallback to using require.resolve if the package defines an exports field
* see https://github.com/sindresorhus/resolve-pkg/issues/9
**/
function resolvePkgRoot(basePackage: string) {
const packageRoot = resolvePkg(basePackage)
if (packageRoot) {
return packageRoot
}
try {
const packageExport = require.resolve(basePackage)
const endIndex = packageExport.indexOf(basePackage) + basePackage.length
return packageExport.substring(0, endIndex)
} catch {}
return undefined
}
/** /**
* Resolve an import path to a file path. * Resolve an import path to a file path.
* @param importPath - The import path to resolve. * @param importPath - The import path to resolve.
@@ -150,7 +166,7 @@ export function tryResolveImport(importPath: string) {
const specifiedVersion = match[2] // "1.2.3" (optional) const specifiedVersion = match[2] // "1.2.3" (optional)
const relativePath = match[3] // "/path/to/file.sol" const relativePath = match[3] // "/path/to/file.sol"
const packageRoot = resolvePkg(basePackage) const packageRoot = resolvePkgRoot(basePackage)
if (!packageRoot) { if (!packageRoot) {
throw new Error(`Package ${basePackage} not found.`) throw new Error(`Package ${basePackage} not found.`)
} }
+1349 -1
View File
File diff suppressed because it is too large Load Diff