Compare commits

...

10 Commits

Author SHA1 Message Date
kvpanch f549a6b031 Infer full LLVM release version from git's tags (#437)
The helps to remove special casing from actions and will make future
LLVM updates smoother.
2025-12-22 16:23:29 -05:00
xermicus be6f734cfc Use the EVM call gas syscall variants (#436)
- Emit the `call_evm` and `delegate_call_evm` syscalls for contract
calls.
- The call gas is no longer ignored.

---------

Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
2025-12-18 17:01:24 +01:00
xermicus 25ee4eef5a dedicated safe integer truncation integration test (#435)
Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
2025-12-17 10:25:41 +01:00
xermicus 9446132608 Emit consume_all_gas in invalid and bounds checks (#433)
Closes #374

Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
2025-12-16 10:04:51 +01:00
xermicus 91bd1b0b4e Do not build the book during test (#432)
Avoid spurious changes in the build output directory.
2025-12-15 14:16:03 +01:00
xermicus e568a924ae update to polkadot-sdk unstable2507 (#431)
Support for `polkadot-sdk` release `unstable2507`. This release will be
deployed to Kusama and is supposed the first one on Polkadot.

---------

Signed-off-by: xermicus <cyrill@parity.io>
Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
2025-12-15 14:02:48 +01:00
xermicus 11f82c8488 Support solc v0.8.31 (#430)
- Support for solc v0.8.31.
- Support for the `clz` Yul builtin.

---------

Signed-off-by: xermicus <cyrill@parity.io>
2025-12-05 15:25:13 +01:00
kvpanch d0c10e6d5c Update ubuntu's source list to use nobel (#425)
This helps to install clang-18 and not clang-15. Latter cannot be used
anymore to build new LLVM due to some missed clang option.
2025-12-03 21:28:48 -05:00
xermicus 45ceab7dc7 Additional bounds check in sbrk (#428)
Close #356

Signed-off-by: xermicus <cyrill@parity.io>
2025-12-03 16:00:19 +01:00
xermicus a9ccb1f9b4 small docs fixes (#426)
Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
2025-12-02 18:01:01 +01:00
68 changed files with 1532 additions and 1343 deletions
+13 -15
View File
@@ -20,24 +20,22 @@ runs:
shell: bash
run: |
if [ -z "${{ inputs.version }}" ]; then
# Extract branch from .gitmodules (e.g., "release/18.x")
BRANCH=$(git config -f .gitmodules submodule.llvm.branch)
if [ -n "$BRANCH" ]; then
# Extract version from branch name (e.g., "18.x" from "release/18.x")
VERSION_PREFIX=$(echo "$BRANCH" | sed 's|release/||' | sed 's|\.x$||')
echo "Detected LLVM version prefix from submodule branch: $VERSION_PREFIX"
# Special case: pin LLVM 18 to specific version 18.1.8
if [ "$VERSION_PREFIX" = "18" ]; then
echo "Using pinned version for LLVM 18: llvm-18.1.8"
echo "version_prefix=llvm-18.1.8" >> $GITHUB_OUTPUT
else
echo "version_prefix=llvm-$VERSION_PREFIX" >> $GITHUB_OUTPUT
fi
# Get the full version from the LLVM submodule.
git submodule update --init --recursive
cd llvm
# Try to get the most recent tag (e.g., "llvmorg-18.1.8")
LLVM_TAG=$(git describe --tags --abbrev=0 2>/dev/null)
if [ -n "$LLVM_TAG" ]; then
echo "Detected LLVM version from submodule: $LLVM_TAG"
# Convert "llvmorg-x.y.z" to "llvm-x.y.z"
LLVM_VERSION=$(echo "$LLVM_TAG" | sed 's/^llvmorg-/llvm-/')
echo "Detected LLVM version from submodule: $LLVM_VERSION"
echo "version_prefix=$LLVM_VERSION" >> $GITHUB_OUTPUT
else
echo "No branch found in .gitmodules, will use latest release"
echo "Could not detect LLVM version from submodule, will use latest release"
echo "version_prefix=" >> $GITHUB_OUTPUT
fi
cd ..
else
echo "Using explicitly provided version: ${{ inputs.version }}"
echo "version_prefix=${{ inputs.version }}" >> $GITHUB_OUTPUT
+1 -1
View File
@@ -19,7 +19,7 @@ runs:
shell: bash
run: |
mkdir -p solc
curl -sSL --output solc/solc https://github.com/ethereum/solidity/releases/download/v0.8.30/${SOLC_NAME}
curl -sSL --output solc/solc https://github.com/ethereum/solidity/releases/download/v0.8.31/${SOLC_NAME}
- name: Make Solc Executable
if: ${{ runner.os == 'Windows' }}
+4 -1
View File
@@ -25,9 +25,12 @@ jobs:
- uses: actions/checkout@v4
with: { fetch-depth: 0 }
- name: Install mdBook
- name: Install and test the mdBook
run: make test-book
- name: Build book
run: mdbook build book
- name: Build book to tmp
run: mdbook build book -d docs-tmp
+4 -1
View File
@@ -82,7 +82,10 @@ jobs:
- name: Install Dependencies
if: ${{ matrix.host == 'linux' }}
run: |
sudo apt-get update && sudo apt-get install -y cmake ninja-build curl git libssl-dev pkg-config clang lld musl
cat /etc/apt/sources.list
sudo sed -i 's/jammy/noble/g' /etc/apt/sources.list
cat /etc/apt/sources.list
sudo apt-get update && sudo apt-get install -y cmake ninja-build curl git libssl-dev pkg-config clang lld musl xz-utils libc6-dev gcc-multilib
- name: Install Dependencies
if: ${{ matrix.host == 'macos' }}
+1 -1
View File
@@ -177,7 +177,7 @@ jobs:
- name: Basic Sanity Check
run: |
mkdir -p solc
curl -sSLo solc/soljson.js https://github.com/ethereum/solidity/releases/download/v0.8.30/soljson.js
curl -sSLo solc/soljson.js https://github.com/ethereum/solidity/releases/download/v0.8.31/soljson.js
node -e "
const soljson = require('solc/soljson');
const createRevive = require('./target/wasm32-unknown-emscripten/release/resolc.js');
+8 -1
View File
@@ -4,17 +4,24 @@
This is a development pre-release.
Supported `polkadot-sdk` rev: `2509.0.0`
Supported `polkadot-sdk` rev: `unstable2507`
### Added
- The comprehensive revive compiler book documentation page: https://paritytech.github.io/revive/
- Support for solc v0.8.31.
- Support for the `clz` Yul builtin.
### Changed
- Instruct the LLVM backend and linker to `--relax` (may lead to smaller contract code size).
- Standard JSON mode: Don't forward EVM bytecode related output selections to solc.
- The supported `polkadot-sdk` release is `unstable2507`.
- The `INVALID` opcode and OOB memory accesses now consume all remaining gas.
- Emit the `call_evm` and `delegate_call_evm` syscalls for contract calls.
### Fixed:
- The missing `STOP` instruction at the end of `code` blocks.
- The missing bounds check in the internal sbrk implementation.
- The call gas is no longer ignored.
## v0.5.0
Generated
+1023 -1026
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -76,7 +76,7 @@ normpath = "1.5"
# polkadot-sdk and friends
codec = { version = "3.7.5", default-features = false, package = "parity-scale-codec" }
scale-info = { version = "2.11.6", default-features = false }
polkadot-sdk = { version = "2509.0.0" }
polkadot-sdk = { version = "=2507.4.0" }
# llvm
[workspace.dependencies.inkwell]
-1
View File
@@ -92,7 +92,6 @@ test-llvm-builder:
test-book:
cargo install mdbook --version 0.5.1 --locked
mdbook test book
mdbook build book
bench: install-bin
cargo criterion --all --all-features --message-format=json \
+1 -1
View File
@@ -45,7 +45,7 @@ Unlike with the EVM, where heap memory usage is gas metered, our heap size is st
LLVM is a special non Rust dependency. We interface its builder interface via the [inkwell](https://crates.io/crates/inkwell) wrapper crate.
We use upstream LLVM, but release and use our custom builds. We require the compiler builtins specifically built for the PVM rv64e target and always leave assertions on. Furthermore, we need cross builds because `resolc` itself targets emscripten and musl. The [revive-llvm-builer](https://crates.io/crates/revive-llvm-builder) functions as a cross-platform build script and is used to build and release the LLVM dependency.
We use upstream LLVM, but release and use our custom builds. We require the compiler builtins specifically built for the PVM `rv64emacb` target and always leave assertions on. Furthermore, we need cross builds because `resolc` itself targets emscripten and musl. The [revive-llvm-builer](https://crates.io/crates/revive-llvm-builder) functions as a cross-platform build script and is used to build and release the LLVM dependency.
We also maintain the [lld-sys crate](https://crates.io/crates/lld-sys) for interfacing with `LLD`. The LLVM linker is used during the compilation process, but we don't want to distribute another binary.
+2 -2
View File
@@ -21,7 +21,7 @@ Valid levels are the following:
- `s`: Optimize for code size.
- `z`: Aggressively optimize for code size.
By default, `-O3` is applied.
By default, `-Oz` is applied.
### Stack size
```bash
@@ -31,7 +31,7 @@ By default, `-O3` is applied.
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.
You are incentivized to keep this value as small as possible:
1. Increasing the stack size will increase startup costs.
1. Increasing the stack size will increase gas costs due to increased startup costs.
2. The stack size contributes to the total memory size a contract can use, which includes the contract's code size.
Default value: 32768
+10
View File
@@ -43,6 +43,12 @@ pub enum EVMVersion {
/// The corresponding EVM version.
#[serde(rename = "cancun")]
Cancun,
/// The corresponding EVM version.
#[serde(rename = "prague")]
Prague,
/// The corresponding EVM version.
#[serde(rename = "osaka")]
Osaka,
}
impl TryFrom<&str> for EVMVersion {
@@ -62,6 +68,8 @@ impl TryFrom<&str> for EVMVersion {
"paris" => Self::Paris,
"shanghai" => Self::Shanghai,
"cancun" => Self::Cancun,
"prague" => Self::Prague,
"osaka" => Self::Osaka,
_ => anyhow::bail!("Invalid EVM version: {}", value),
})
}
@@ -82,6 +90,8 @@ impl std::fmt::Display for EVMVersion {
Self::Paris => write!(f, "paris"),
Self::Shanghai => write!(f, "shanghai"),
Self::Cancun => write!(f, "cancun"),
Self::Prague => write!(f, "prague"),
Self::Osaka => write!(f, "osaka"),
}
}
}
+12
View File
@@ -15,6 +15,8 @@
"grayGlacierBlock": 0,
"shanghaiTime": 0,
"cancunTime": 0,
"pragueTime": 0,
"osakaTime": 0,
"terminalTotalDifficulty": 0,
"terminalTotalDifficultyPassed": true,
"blobSchedule": {
@@ -22,6 +24,16 @@
"target": 3,
"max": 6,
"baseFeeUpdateFraction": 3338477
},
"prague": {
"target": 6,
"max": 9,
"baseFeeUpdateFraction": 5007716
},
"osaka": {
"target": 6,
"max": 9,
"baseFeeUpdateFraction": 5007716
}
}
},
+8 -8
View File
@@ -1,10 +1,10 @@
{
"Baseline": 905,
"Computation": 2286,
"DivisionArithmetics": 14347,
"ERC20": 16929,
"Events": 1665,
"FibonacciIterative": 1447,
"Flipper": 2077,
"SHA1": 7721
"Baseline": 911,
"Computation": 2337,
"DivisionArithmetics": 14488,
"ERC20": 17041,
"Events": 1672,
"FibonacciIterative": 1454,
"Flipper": 2106,
"SHA1": 7814
}
+1 -1
View File
@@ -26,6 +26,6 @@ pragma solidity ^0.8;
contract BaseFee {
constructor() payable {
assert(block.basefee == 0);
assert(block.basefee > 0);
}
}
+50
View File
@@ -0,0 +1,50 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8;
// Use a non-zero call gas that works with call gas clipping but not with a truncate.
/* runner.json
{
"differential": true,
"actions": [
{
"Upload": {
"code": {
"Solidity": {
"contract": "Other"
}
}
}
},
{
"Instantiate": {
"code": {
"Solidity": {
"contract": "CallGas"
}
},
"data": "1000000000000000000000000000000000000000000000000000000000000001"
}
}
]
}
*/
contract Other {
address public last;
uint public foo;
fallback() external {
last = msg.sender;
foo += 1;
}
}
contract CallGas {
constructor(uint _gas) payable {
Other other = new Other();
address(other).call{ gas: _gas }(hex"");
assert(other.last() == address(this));
}
}
@@ -0,0 +1,57 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.31;
/* runner.json
{
"differential": true,
"actions": [
{
"Instantiate": {
"code": {
"Solidity": {
"contract": "CountLeadingZeros"
}
}
}
}
]
}
*/
/// The EIP-7939 test vectors:
/// https://eips.ethereum.org/EIPS/eip-7939#test-cases
contract CountLeadingZeros {
function clz(uint256 x) internal pure returns (uint256 r) {
assembly {
r := clz(x)
}
}
constructor() payable {
assert(
clz(0x000000000000000000000000000000000000000000000000000000000000000)
== 0x0000000000000000000000000000000000000000000000000000000000000100
);
assert(
clz(0x8000000000000000000000000000000000000000000000000000000000000000)
== 0x0000000000000000000000000000000000000000000000000000000000000000
);
assert(
clz(0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)
== 0x0000000000000000000000000000000000000000000000000000000000000000
);
assert(
clz(0x4000000000000000000000000000000000000000000000000000000000000000)
== 0x0000000000000000000000000000000000000000000000000000000000000001
);
assert(
clz(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)
== 0x0000000000000000000000000000000000000000000000000000000000000001
);
assert(
clz(0x0000000000000000000000000000000000000000000000000000000000000001)
== 0x00000000000000000000000000000000000000000000000000000000000000ff
);
}
}
-1
View File
@@ -26,7 +26,6 @@ pragma solidity ^0.8;
contract GasLeft {
constructor() payable {
assert(gasleft() > gasleft());
assert(gasleft() > 0 && gasleft() < 0xffffffffffffffff);
}
}
+1 -1
View File
@@ -26,6 +26,6 @@ pragma solidity ^0.8;
contract GasLimit {
constructor() payable {
assert(block.gaslimit == 2000000000000);
assert(block.gaslimit > 0);
}
}
+1 -1
View File
@@ -26,6 +26,6 @@ pragma solidity ^0.8;
contract GasPrice {
constructor() payable {
assert(tx.gasprice == 1000);
assert(tx.gasprice > 1000);
}
}
+4 -4
View File
@@ -20,7 +20,7 @@ pragma solidity ^0.8.28;
"dest": {
"Instantiated": 0
},
"data": "e2179b8e"
"data": "0be0e4a60000000000000000000000000000000000000000000000000000000000000000"
}
}
]
@@ -29,12 +29,12 @@ pragma solidity ^0.8.28;
contract MLoad {
constructor() payable {
assert(g() == 0);
assert(loadAt(0) == 0);
}
function g() public payable returns (uint m) {
function loadAt(uint _offset) public payable returns (uint m) {
assembly {
m := mload(0)
m := mload(_offset)
}
}
}
-15
View File
@@ -23,11 +23,6 @@ pragma solidity ^0.8;
"data": "1c8d16b30000000000000000000000000303030303030303030303030303030303030303000000000000000000000000000000000000000000000000000000000000000a"
}
},
{
"VerifyCall": {
"success": true
}
},
{
"Call": {
"dest": {
@@ -36,11 +31,6 @@ pragma solidity ^0.8;
"data": "fb9e8d050000000000000000000000000000000000000000000000000000000000000001"
}
},
{
"VerifyCall": {
"success": false
}
},
{
"Call": {
"dest": {
@@ -48,11 +38,6 @@ pragma solidity ^0.8;
},
"data": "fb9e8d050000000000000000000000000000000000000000000000000000000000000000"
}
},
{
"VerifyCall": {
"success": false
}
}
]
}
-15
View File
@@ -23,11 +23,6 @@ pragma solidity ^0.8;
"data": "1c8d16b30000000000000000000000000303030303030303030303030303030303030303000000000000000000000000000000000000000000000000000000000000000a"
}
},
{
"VerifyCall": {
"success": true
}
},
{
"Call": {
"dest": {
@@ -36,11 +31,6 @@ pragma solidity ^0.8;
"data": "fb9e8d050000000000000000000000000000000000000000000000000000000000000001"
}
},
{
"VerifyCall": {
"success": false
}
},
{
"Call": {
"dest": {
@@ -48,11 +38,6 @@ pragma solidity ^0.8;
},
"data": "fb9e8d050000000000000000000000000000000000000000000000000000000000000000"
}
},
{
"VerifyCall": {
"success": false
}
}
]
}
+9
View File
@@ -234,6 +234,15 @@ sol!(
);
case!("MCopy.sol", MCopy, memcpyCall, memcpy, payload: Bytes);
sol!(
contract MLoad {
constructor() payable;
function loadAt(uint _offset) public payable returns (uint m);
}
);
case!("MLoad.sol", MLoad, loadAtCall, load_at, _offset: U256);
sol!(
contract Call {
function value_transfer(address payable destination) public payable;
+134 -44
View File
@@ -65,6 +65,8 @@ test_spec!(shift_arithmetic_right, "SAR", "SAR.sol");
test_spec!(add_mod_mul_mod, "AddModMulModTester", "AddModMulMod.sol");
test_spec!(memory_bounds, "MemoryBounds", "MemoryBounds.sol");
test_spec!(selfdestruct, "Selfdestruct", "Selfdestruct.sol");
test_spec!(clz, "CountLeadingZeros", "CountLeadingZeros.sol");
test_spec!(call_gas, "CallGas", "CallGas.sol");
fn instantiate(path: &str, contract: &str) -> Vec<SpecsAction> {
vec![Instantiate {
@@ -450,50 +452,6 @@ fn ext_code_size() {
.run();
}
#[test]
#[should_panic(expected = "ReentranceDenied")]
fn send_denies_reentrancy() {
let value = 1000;
Specs {
actions: vec![
instantiate("contracts/Send.sol", "Send").remove(0),
Call {
origin: TestAddress::Alice,
dest: TestAddress::Instantiated(0),
value,
gas_limit: None,
storage_deposit_limit: None,
data: Contract::send_self(U256::from(value)).calldata,
},
],
differential: false,
..Default::default()
}
.run();
}
#[test]
#[should_panic(expected = "ReentranceDenied")]
fn transfer_denies_reentrancy() {
let value = 1000;
Specs {
actions: vec![
instantiate("contracts/Transfer.sol", "Transfer").remove(0),
Call {
origin: TestAddress::Alice,
dest: TestAddress::Instantiated(0),
value,
gas_limit: None,
storage_deposit_limit: None,
data: Contract::transfer_self(U256::from(value)).calldata,
},
],
differential: false,
..Default::default()
}
.run();
}
#[test]
fn create2_salt() {
let salt = U256::from(777);
@@ -619,3 +577,135 @@ fn code_block_with_nested_object_stops() {
}
.run();
}
#[test]
fn sbrk_bounds_checks() {
let code = &build_yul(&[(
"poc.yul",
r#"object "Test" {
code {
return(0x4, 0xffffffff)
stop()
}
object "Test_deployed" {
code {
stop()
}
}
}"#,
)])
.unwrap()["poc.yul:Test"];
let results = Specs {
actions: vec![
Instantiate {
origin: TestAddress::Alice,
value: 0,
gas_limit: Some(GAS_LIMIT),
storage_deposit_limit: None,
code: Code::Bytes(code.to_vec()),
data: Default::default(),
salt: OptionalHex::default(),
},
VerifyCall(VerifyCallExpectation {
success: false,
..Default::default()
}),
],
differential: false,
..Default::default()
}
.run();
let CallResult::Instantiate { result, .. } = results.last().unwrap() else {
unreachable!()
};
assert!(
format!("{result:?}").contains("ContractTrapped"),
"not seeing a trap means the contract did not catch the OOB"
);
}
#[test]
fn invalid_opcode_works() {
let code = &build_yul(&[(
"invalid.yul",
r#"object "Test" {
code {
invalid()
}
object "Test_deployed" {
code {
invalid()
}
}
}"#,
)])
.unwrap()["invalid.yul:Test"];
let results = Specs {
actions: vec![
Instantiate {
origin: TestAddress::Alice,
value: 0,
gas_limit: Some(GAS_LIMIT),
storage_deposit_limit: None,
code: Code::Bytes(code.to_vec()),
data: Default::default(),
salt: OptionalHex::default(),
},
VerifyCall(VerifyCallExpectation {
success: false,
..Default::default()
}),
],
differential: false,
..Default::default()
}
.run();
let CallResult::Instantiate { result, .. } = results.last().unwrap() else {
unreachable!()
};
assert_eq!(result.weight_consumed, GAS_LIMIT);
}
/// Load from heap memory using an out of bounds offset and expect the
/// contract to hit the `invalid` syscall to use all gas (like on EVM).
///
/// The offset is picked such that a regular truncate would be in bounds.
#[test]
fn safe_truncate_int_to_xlen_works() {
let offset = 0x10000000_00000000u64;
let data = Contract::load_at(Uint::from(offset)).calldata;
let mut actions = instantiate("contracts/MLoad.sol", "MLoad");
actions.append(&mut vec![
Call {
origin: TestAddress::Alice,
dest: TestAddress::Instantiated(0),
value: 0,
gas_limit: None,
storage_deposit_limit: None,
data,
},
VerifyCall(VerifyCallExpectation {
success: false,
..Default::default()
}),
]);
let results = Specs {
actions,
differential: true,
..Default::default()
}
.run();
let CallResult::Exec { result, .. } = results.last().unwrap() else {
unreachable!()
};
assert_eq!(result.weight_consumed, GAS_LIMIT);
}
@@ -14,6 +14,8 @@ pub struct Intrinsics<'ctx> {
pub byte_swap_word: FunctionDeclaration<'ctx>,
/// Performs endianness swaps on i160 values
pub byte_swap_eth_address: FunctionDeclaration<'ctx>,
/// Counts leading zeroes.
pub count_leading_zeros: FunctionDeclaration<'ctx>,
}
impl<'ctx> Intrinsics<'ctx> {
@@ -26,6 +28,9 @@ impl<'ctx> Intrinsics<'ctx> {
/// The corresponding intrinsic function name.
pub const FUNCTION_BYTE_SWAP_ETH_ADDRESS: &'static str = "llvm.bswap.i160";
/// The corresponding intrinsic function name.
pub const FUNCTION_COUNT_LEADING_ZEROS: &'static str = "llvm.ctlz.i256";
/// A shortcut constructor.
pub fn new(
llvm: &'ctx inkwell::context::Context,
@@ -53,11 +58,18 @@ impl<'ctx> Intrinsics<'ctx> {
Self::FUNCTION_BYTE_SWAP_ETH_ADDRESS,
address_type.fn_type(&[address_type.as_basic_type_enum().into()], false),
);
let count_leading_zeros = Self::declare(
llvm,
module,
Self::FUNCTION_COUNT_LEADING_ZEROS,
word_type.fn_type(&[word_type.into(), llvm.bool_type().into()], false),
);
Self {
trap,
byte_swap_word,
byte_swap_eth_address,
count_leading_zeros,
}
}
@@ -85,12 +97,15 @@ impl<'ctx> Intrinsics<'ctx> {
let word_type = llvm.custom_width_int_type(revive_common::BIT_LENGTH_WORD as u32);
match name {
name if name == Self::FUNCTION_BYTE_SWAP_WORD => vec![word_type.as_basic_type_enum()],
name if name == Self::FUNCTION_BYTE_SWAP_ETH_ADDRESS => {
_ if name == Self::FUNCTION_BYTE_SWAP_WORD => vec![word_type.as_basic_type_enum()],
_ if name == Self::FUNCTION_BYTE_SWAP_ETH_ADDRESS => {
vec![llvm
.custom_width_int_type(revive_common::BIT_LENGTH_ETH_ADDRESS as u32)
.as_basic_type_enum()]
}
_ if name == Self::FUNCTION_COUNT_LEADING_ZEROS => {
vec![word_type.as_basic_type_enum()]
}
_ => vec![],
}
}
@@ -51,11 +51,11 @@ impl RuntimeFunction for WordToPointer {
)?;
let block_continue = context.append_basic_block("offset_pointer_ok");
let block_trap = context.append_basic_block("offset_pointer_overflow");
context.build_conditional_branch(is_overflow, block_trap, block_continue)?;
let block_invalid = context.append_basic_block("offset_pointer_overflow");
context.build_conditional_branch(is_overflow, block_invalid, block_continue)?;
context.set_basic_block(block_trap);
context.build_call(context.intrinsics().trap, &[], "invalid_trap");
context.set_basic_block(block_invalid);
context.build_runtime_call(revive_runtime_api::polkavm_imports::INVALID, &[]);
context.build_unreachable();
context.set_basic_block(block_continue);
@@ -77,6 +77,20 @@ impl RuntimeFunction for Sbrk {
context.build_unreachable();
context.set_basic_block(offset_in_bounds_block);
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,
size,
context.heap_size(),
"size_in_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 mask = context
.xlen_type()
.const_int(BYTE_LENGTH_WORD as u64 - 1, false);
@@ -88,20 +102,20 @@ impl RuntimeFunction for Sbrk {
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(
let total_size_in_bounds_block = context.append_basic_block("total_size_in_bounds");
let is_total_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,
is_total_size_out_of_bounds,
trap_block,
size_in_bounds_block,
total_size_in_bounds_block,
)?;
context.set_basic_block(size_in_bounds_block);
context.set_basic_block(total_size_in_bounds_block);
let new_size_block = context.append_basic_block("new_size");
let is_new_size = context.builder().build_int_compare(
inkwell::IntPredicate::UGT,
@@ -261,3 +261,20 @@ pub fn byte<'ctx>(
Ok(byte.as_basic_value_enum())
}
/// Translates the CLZ instruction.
pub fn count_leading_zeros<'ctx>(
context: &mut Context<'ctx>,
value: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>> {
Ok(context
.builder()
.build_call(
context.intrinsics().count_leading_zeros.function_value(),
&[value.into(), context.bool_const(false).into()],
"clz",
)?
.try_as_basic_value()
.left()
.expect("the llvm.ctlz should return a value"))
}
+27 -122
View File
@@ -4,9 +4,8 @@ use inkwell::values::BasicValue;
use crate::polkavm::context::Context;
const STATIC_CALL_FLAG: u32 = 0b0001_0000;
const REENTRANT_CALL_FLAG: u32 = 0b0000_1000;
const SOLIDITY_TRANSFER_GAS_STIPEND_THRESHOLD: u64 = 2300;
const STATIC_CALL_FLAG: u64 = 0b0001_0000;
const REENTRANT_CALL_FLAG: u64 = 0b0000_1000;
/// Translates a contract call.
pub fn call<'ctx>(
@@ -38,33 +37,12 @@ pub fn call<'ctx>(
let output_length_pointer = context.build_alloca_at_entry(context.xlen_type(), "output_length");
context.build_store(output_length_pointer, output_length)?;
let (flags, deposit_limit_value) = if static_call {
let flags = REENTRANT_CALL_FLAG | STATIC_CALL_FLAG;
(
context.xlen_type().const_int(flags as u64, false),
context.word_type().const_zero(),
)
let flags = if static_call {
REENTRANT_CALL_FLAG | STATIC_CALL_FLAG
} else {
call_reentrancy_heuristic(context, gas, input_length, output_length)?
REENTRANT_CALL_FLAG
};
let deposit_pointer = context.build_alloca_at_entry(context.word_type(), "deposit_pointer");
context.build_store(deposit_pointer, deposit_limit_value)?;
let flags_and_callee = revive_runtime_api::calling_convention::pack_hi_lo_reg(
context.builder(),
context.llvm(),
flags,
address_pointer.to_int(context),
"address_and_callee",
)?;
let deposit_and_value = revive_runtime_api::calling_convention::pack_hi_lo_reg(
context.builder(),
context.llvm(),
deposit_pointer.to_int(context),
value_pointer.to_int(context),
"deposit_and_value",
)?;
let input_data = revive_runtime_api::calling_convention::pack_hi_lo_reg(
context.builder(),
context.llvm(),
@@ -85,10 +63,10 @@ pub fn call<'ctx>(
.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(),
context.xlen_type().const_int(flags, false).into(),
address_pointer.to_int(context).into(),
value_pointer.to_int(context).into(),
clip_call_gas(context, gas)?,
input_data.into(),
output_data.into(),
],
@@ -111,7 +89,7 @@ pub fn call<'ctx>(
pub fn delegate_call<'ctx>(
context: &mut Context<'ctx>,
_gas: inkwell::values::IntValue<'ctx>,
gas: inkwell::values::IntValue<'ctx>,
address: inkwell::values::IntValue<'ctx>,
input_offset: inkwell::values::IntValue<'ctx>,
input_length: inkwell::values::IntValue<'ctx>,
@@ -132,18 +110,6 @@ pub fn delegate_call<'ctx>(
let output_length_pointer = context.build_alloca_at_entry(context.xlen_type(), "output_length");
context.build_store(output_length_pointer, output_length)?;
let deposit_pointer = context.build_alloca_at_entry(context.word_type(), "deposit_pointer");
context.build_store(deposit_pointer, context.word_type().const_all_ones())?;
let flags = context.xlen_type().const_int(0u64, false);
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 input_data = revive_runtime_api::calling_convention::pack_hi_lo_reg(
context.builder(),
context.llvm(),
@@ -164,10 +130,9 @@ pub fn delegate_call<'ctx>(
.build_runtime_call(
name,
&[
flags_and_callee.into(),
context.register_type().const_all_ones().into(),
context.register_type().const_all_ones().into(),
deposit_pointer.to_int(context).into(),
context.xlen_type().const_int(0u64, false).into(),
address_pointer.to_int(context).into(),
clip_call_gas(context, gas)?,
input_data.into(),
output_data.into(),
],
@@ -201,82 +166,22 @@ pub fn linker_symbol<'ctx>(
context.build_load_address(context.get_global(path)?.into())
}
/// 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>(
context: &mut Context<'ctx>,
/// The runtime implements gas as `u64` so we clip the stipend to `u64::MAX`.
fn clip_call_gas<'ctx>(
context: &Context<'ctx>,
gas: inkwell::values::IntValue<'ctx>,
input_length: inkwell::values::IntValue<'ctx>,
output_length: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<(
inkwell::values::IntValue<'ctx>,
inkwell::values::IntValue<'ctx>,
)> {
// Branch-free SSA implementation: First derive the heuristic boolean (int1) value.
let input_length_or_output_length =
context
.builder()
.build_or(input_length, output_length, "input_length_or_output_length")?;
let is_no_input_no_output = context.builder().build_int_compare(
inkwell::IntPredicate::EQ,
context.xlen_type().const_zero(),
input_length_or_output_length,
"is_no_input_no_output",
)?;
let gas_stipend = context
.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,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>> {
let builder = context.builder();
let clipped = context.register_type().const_all_ones();
let is_overflow = builder.build_int_compare(
inkwell::IntPredicate::UGT,
gas,
gas_stipend,
"is_gas_stipend_for_transfer_or_send",
builder.build_int_z_extend(clipped, context.word_type(), "gas_clipped")?,
"is_gas_overflow",
)?;
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")?;
let truncated = builder.build_int_truncate(gas, context.register_type(), "gas_truncated")?;
let call_gas = builder.build_select(is_overflow, clipped, truncated, "call_gas")?;
// 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))
Ok(call_gas)
}
@@ -52,12 +52,14 @@ pub fn stop(context: &mut Context) -> anyhow::Result<()> {
/// Translates the `invalid` instruction.
/// Burns all gas using an out-of-bounds memory store, causing a panic.
pub fn invalid(context: &mut Context) -> anyhow::Result<()> {
crate::polkavm::evm::memory::store(
context,
context.word_type().const_all_ones(),
context.word_const(0),
)?;
context.build_call(context.intrinsics().trap, &[], "invalid_trap");
let invalid_block = context.append_basic_block("explicit_invalid");
context.build_unconditional_branch(invalid_block);
context.set_basic_block(invalid_block);
context.build_runtime_call(revive_runtime_api::polkavm_imports::INVALID, &[]);
context.build_unreachable();
context.set_basic_block(context.append_basic_block("dead_code"));
Ok(())
}
+1 -1
View File
@@ -23,7 +23,7 @@ pub mod version;
pub const FIRST_SUPPORTED_VERSION: semver::Version = semver::Version::new(0, 8, 0);
/// The last supported version of `solc`.
pub const LAST_SUPPORTED_VERSION: semver::Version = semver::Version::new(0, 8, 30);
pub const LAST_SUPPORTED_VERSION: semver::Version = semver::Version::new(0, 8, 31);
/// The Solidity compiler.
pub trait Compiler {
+10 -5
View File
@@ -59,7 +59,7 @@ pub const CHARLIE: H160 = H160([3u8; 20]);
/// Default gas limit
pub const GAS_LIMIT: Weight = Weight::from_parts(100_000_000_000_000, 3 * 1024 * 1024 * 1024);
/// Default deposit limit
pub const DEPOSIT_LIMIT: Balance = 10_000_000;
pub const DEPOSIT_LIMIT: Balance = 100_000_000_000;
/// The native to ETH balance factor.
pub const ETH_RATIO: Balance = 1_000_000;
@@ -97,14 +97,19 @@ impl ExtBuilder {
.unwrap();
let mut ext = sp_io::TestExternalities::new(t);
let checking_account = Pallet::<Runtime>::account_id();
ext.register_extension(KeystoreExt::new(MemoryKeystore::new()));
ext.execute_with(|| {
let _ = <Runtime as Config>::Currency::deposit_creating(
&Pallet::<Runtime>::account_id(),
<Runtime as Config>::Currency::minimum_balance(),
&checking_account,
1_000_000_000_000,
);
System::set_block_number(1);
assert_ok!(Pallet::<Runtime>::map_account(RuntimeOrigin::signed(
checking_account
)));
});
ext
@@ -115,7 +120,7 @@ impl ExtBuilder {
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VerifyCallExpectation {
/// When provided, the expected gas consumed
pub gas_consumed: Option<Weight>,
pub gas_consumed: Option<u128>,
/// When provided, the expected output
#[serde(default, with = "hex")]
pub output: OptionalHex<Vec<u8>>,
@@ -238,7 +243,7 @@ impl CallResult {
}
/// Get the gas consumed by the call
fn gas_consumed(&self) -> Weight {
fn gas_consumed(&self) -> u128 {
match self {
Self::Exec { result, .. } => result.gas_consumed,
Self::Instantiate { result, .. } => result.gas_consumed,
+2 -1
View File
@@ -2,7 +2,7 @@ use frame_support::{runtime, traits::FindAuthor, weights::constants::WEIGHT_REF_
use pallet_revive::AccountId32Mapper;
use polkadot_sdk::*;
use polkadot_sdk::{
polkadot_sdk_frame::{log, runtime::prelude::*},
polkadot_sdk_frame::runtime::prelude::*,
sp_runtime::{AccountId32, Perbill},
};
@@ -72,6 +72,7 @@ parameter_types! {
#[derive_impl(pallet_revive::config_preludes::TestDefaultConfig)]
impl pallet_revive::Config for Runtime {
type Balance = Balance;
type Time = Timestamp;
type Currency = Balances;
type DepositPerByte = DepositPerByte;
+14 -9
View File
@@ -1,6 +1,6 @@
use std::{str::FromStr, time::Instant};
use polkadot_sdk::pallet_revive::Pallet;
use polkadot_sdk::pallet_revive::{ExecConfig, Pallet, TransactionLimits};
use serde::{Deserialize, Serialize};
use crate::*;
@@ -210,9 +210,9 @@ impl Default for Specs {
Self {
differential: false,
balances: vec![
(ALICE, 1_000_000_000),
(BOB, 1_000_000_000),
(CHARLIE, 1_000_000_000),
(ALICE, 1_000_000_000_000),
(BOB, 1_000_000_000_000),
(CHARLIE, 1_000_000_000_000),
],
actions: Default::default(),
}
@@ -447,12 +447,14 @@ impl Specs {
let result = Contracts::bare_instantiate(
origin,
value.into(),
gas_limit.unwrap_or(GAS_LIMIT),
storage_deposit_limit.unwrap_or(DEPOSIT_LIMIT).into(),
TransactionLimits::WeightAndDeposit {
weight_limit: gas_limit.unwrap_or(GAS_LIMIT),
deposit_limit: storage_deposit_limit.unwrap_or(DEPOSIT_LIMIT),
},
code,
data,
salt.0,
pallet_revive::BumpNonce::No,
ExecConfig::new_substrate_tx(),
);
results.push(CallResult::Instantiate {
result,
@@ -486,9 +488,12 @@ impl Specs {
RuntimeOrigin::signed(origin.to_account_id(&results)),
dest.to_eth_addr(&results),
value.into(),
gas_limit.unwrap_or(GAS_LIMIT),
storage_deposit_limit.unwrap_or(DEPOSIT_LIMIT).into(),
TransactionLimits::WeightAndDeposit {
weight_limit: gas_limit.unwrap_or(GAS_LIMIT),
deposit_limit: storage_deposit_limit.unwrap_or(DEPOSIT_LIMIT),
},
data,
ExecConfig::new_substrate_tx(),
);
results.push(CallResult::Exec {
result,
+4 -2
View File
@@ -53,7 +53,7 @@ POLKAVM_IMPORT(void, block_hash, uint32_t, uint32_t)
POLKAVM_IMPORT(void, block_number, uint32_t)
POLKAVM_IMPORT(uint32_t, call, uint64_t, uint64_t, uint64_t, uint64_t, uint64_t, uint64_t)
POLKAVM_IMPORT(uint32_t, call_evm, uint32_t, uint32_t, uint32_t, uint64_t, uint64_t, uint64_t)
POLKAVM_IMPORT(void, call_data_copy, uint32_t, uint32_t, uint32_t)
@@ -69,7 +69,9 @@ POLKAVM_IMPORT(uint64_t, code_size, uint32_t)
POLKAVM_IMPORT(void, code_hash, uint32_t, uint32_t)
POLKAVM_IMPORT(uint32_t, delegate_call, uint64_t, uint64_t, uint64_t, uint32_t, uint64_t, uint64_t)
POLKAVM_IMPORT(void, consume_all_gas)
POLKAVM_IMPORT(uint32_t, delegate_call_evm, uint32_t, uint32_t, uint64_t, uint64_t, uint64_t)
POLKAVM_IMPORT(void, deposit_event, uint32_t, uint32_t, uint32_t, uint32_t)
+6 -3
View File
@@ -16,7 +16,7 @@ pub static BLOCK_HASH: &str = "block_hash";
pub static BLOCK_NUMBER: &str = "block_number";
pub static CALL: &str = "call";
pub static CALL: &str = "call_evm";
pub static CALL_DATA_COPY: &str = "call_data_copy";
@@ -32,7 +32,7 @@ pub static CODE_SIZE: &str = "code_size";
pub static CODE_HASH: &str = "code_hash";
pub static DELEGATE_CALL: &str = "delegate_call";
pub static DELEGATE_CALL: &str = "delegate_call_evm";
pub static DEPOSIT_EVENT: &str = "deposit_event";
@@ -48,6 +48,8 @@ pub static HASH_KECCAK_256: &str = "hash_keccak_256";
pub static INSTANTIATE: &str = "instantiate";
pub static INVALID: &str = "consume_all_gas";
pub static NOW: &str = "now";
pub static ORIGIN: &str = "origin";
@@ -70,7 +72,7 @@ pub static VALUE_TRANSFERRED: &str = "value_transferred";
/// All imported runtime API symbols.
/// Useful for configuring common attributes and linkage.
pub static IMPORTS: [&str; 33] = [
pub static IMPORTS: [&str; 34] = [
ADDRESS,
BALANCE,
BALANCE_OF,
@@ -94,6 +96,7 @@ pub static IMPORTS: [&str; 33] = [
GET_STORAGE,
HASH_KECCAK_256,
INSTANTIATE,
INVALID,
NOW,
ORIGIN,
REF_TIME_LEFT,
@@ -113,10 +113,9 @@ impl Error {
let message = r#"
Warning: It looks like you are using '<address payable>.send/transfer(<X>)'.
Using '<address payable>.send/transfer(<X>)' is deprecated and strongly discouraged!
The resolc compiler uses a heuristic to detect '<address payable>.send/transfer(<X>)' calls,
which disables call re-entrancy and supplies all remaining gas instead of the 2300 gas stipend.
However, detection is not guaranteed. You are advised to carefully test this, employ
re-entrancy guards or use the withdrawal pattern instead!
The revive runtime uses a heuristic to detect '<address payable>.send/transfer(<X>)' calls and
the gas stipend used by the runtime is different from the EVM.
You are advised to carefully test this, employ re-entrancy guards or use the withdrawal pattern instead!
Learn more on https://docs.soliditylang.org/en/latest/security-considerations.html#reentrancy
and https://docs.soliditylang.org/en/latest/common-patterns.html#withdrawal-from-contracts
"#
@@ -348,6 +348,14 @@ impl FunctionCall {
)
.map(Some)
}
Name::Clz => {
let arguments = self.pop_arguments_llvm::<1>(context)?;
revive_llvm_context::polkavm_evm_bitwise::count_leading_zeros(
context,
arguments[0].into_int_value(),
)
.map(Some)
}
Name::Byte => {
let arguments = self.pop_arguments_llvm::<2>(context)?;
revive_llvm_context::polkavm_evm_bitwise::byte(
@@ -54,6 +54,8 @@ pub enum Name {
Shr,
/// signed arithmetic shift right `y` by `x` bits
Sar,
/// number of leading zero bits of x, 256 if x == 0
Clz,
/// `n`th byte of `x`, where the most significant byte is the `0`th byte
Byte,
/// discard value x
@@ -270,6 +272,7 @@ impl From<&str> for Name {
"shl" => Self::Shl,
"shr" => Self::Shr,
"sar" => Self::Sar,
"clz" => Self::Clz,
"byte" => Self::Byte,
"pop" => Self::Pop,
@@ -393,6 +396,7 @@ impl fmt::Display for Name {
Self::Shl => "shl",
Self::Shr => "shr",
Self::Sar => "sar",
Self::Clz => "clz",
Self::Byte => "byte",
Self::Pop => "pop",
+1 -1
View File
@@ -36,7 +36,7 @@
const path_to_root = "";
const default_light_theme = "light";
const default_dark_theme = "navy";
window.path_to_searchindex_js = "searchindex-756a6899.js";
window.path_to_searchindex_js = "searchindex-82723808.js";
</script>
<!-- Start loading toc.js asap -->
<script src="toc-6f3f265e.js"></script>
+1 -1
View File
@@ -35,7 +35,7 @@
const path_to_root = "";
const default_light_theme = "light";
const default_dark_theme = "navy";
window.path_to_searchindex_js = "searchindex-756a6899.js";
window.path_to_searchindex_js = "searchindex-82723808.js";
</script>
<!-- Start loading toc.js asap -->
<script src="toc-6f3f265e.js"></script>
+2 -2
View File
@@ -35,7 +35,7 @@
const path_to_root = "../";
const default_light_theme = "light";
const default_dark_theme = "navy";
window.path_to_searchindex_js = "../searchindex-756a6899.js";
window.path_to_searchindex_js = "../searchindex-82723808.js";
</script>
<!-- Start loading toc.js asap -->
<script src="../toc-6f3f265e.js"></script>
@@ -205,7 +205,7 @@
<p>Unlike with the EVM, where heap memory usage is gas metered, our heap size is static (the size is user controllable via a setting flag). The compiler emits bound checks to prevent overflows.</p>
<h2 id="the-llvm-dependency"><a class="header" href="#the-llvm-dependency">The LLVM dependency</a></h2>
<p>LLVM is a special non Rust dependency. We interface its builder interface via the <a href="https://crates.io/crates/inkwell">inkwell</a> wrapper crate.</p>
<p>We use upstream LLVM, but release and use our custom builds. We require the compiler builtins specifically built for the PVM rv64e target and always leave assertions on. Furthermore, we need cross builds because <code>resolc</code> itself targets emscripten and musl. The <a href="https://crates.io/crates/revive-llvm-builder">revive-llvm-builer</a> functions as a cross-platform build script and is used to build and release the LLVM dependency.</p>
<p>We use upstream LLVM, but release and use our custom builds. We require the compiler builtins specifically built for the PVM <code>rv64emacb</code> target and always leave assertions on. Furthermore, we need cross builds because <code>resolc</code> itself targets emscripten and musl. The <a href="https://crates.io/crates/revive-llvm-builder">revive-llvm-builer</a> functions as a cross-platform build script and is used to build and release the LLVM dependency.</p>
<p>We also maintain the <a href="https://crates.io/crates/lld-sys">lld-sys crate</a> for interfacing with <code>LLD</code>. The LLVM linker is used during the compilation process, but we dont want to distribute another binary.</p>
<h2 id="custom-optimizations"><a class="header" href="#custom-optimizations">Custom optimizations</a></h2>
<p>At the moment, no significant custom optimizations are implemented. Thus, we are missing some optimization opportunities that neither <code>solc</code> nor LLVM can realize (due to their lack of domain specific knowledge about the semantics of our target environment). Furthermore, <code>solc</code> optimizes for EVM gas and a target machine orthogonal to our target (BE 256-bit stack machine EVM vs. 64-bit LE RISC architecture PVM). We have started working on an additional IR layer between Yul and LLVM to capture missed optimization opportunities, though.</p>
+1 -1
View File
@@ -35,7 +35,7 @@
const path_to_root = "../";
const default_light_theme = "light";
const default_dark_theme = "navy";
window.path_to_searchindex_js = "../searchindex-756a6899.js";
window.path_to_searchindex_js = "../searchindex-82723808.js";
</script>
<!-- Start loading toc.js asap -->
<script src="../toc-6f3f265e.js"></script>
+1 -1
View File
@@ -35,7 +35,7 @@
const path_to_root = "../";
const default_light_theme = "light";
const default_dark_theme = "navy";
window.path_to_searchindex_js = "../searchindex-756a6899.js";
window.path_to_searchindex_js = "../searchindex-82723808.js";
</script>
<!-- Start loading toc.js asap -->
<script src="../toc-6f3f265e.js"></script>
+1 -1
View File
@@ -35,7 +35,7 @@
const path_to_root = "../";
const default_light_theme = "light";
const default_dark_theme = "navy";
window.path_to_searchindex_js = "../searchindex-756a6899.js";
window.path_to_searchindex_js = "../searchindex-82723808.js";
</script>
<!-- Start loading toc.js asap -->
<script src="../toc-6f3f265e.js"></script>
+1 -1
View File
@@ -35,7 +35,7 @@
const path_to_root = "../";
const default_light_theme = "light";
const default_dark_theme = "navy";
window.path_to_searchindex_js = "../searchindex-756a6899.js";
window.path_to_searchindex_js = "../searchindex-82723808.js";
</script>
<!-- Start loading toc.js asap -->
<script src="../toc-6f3f265e.js"></script>
+1 -1
View File
@@ -35,7 +35,7 @@
const path_to_root = "";
const default_light_theme = "light";
const default_dark_theme = "navy";
window.path_to_searchindex_js = "searchindex-756a6899.js";
window.path_to_searchindex_js = "searchindex-82723808.js";
</script>
<!-- Start loading toc.js asap -->
<script src="toc-6f3f265e.js"></script>
+1 -1
View File
@@ -35,7 +35,7 @@
const path_to_root = "";
const default_light_theme = "light";
const default_dark_theme = "navy";
window.path_to_searchindex_js = "searchindex-756a6899.js";
window.path_to_searchindex_js = "searchindex-82723808.js";
</script>
<!-- Start loading toc.js asap -->
<script src="toc-6f3f265e.js"></script>
+4 -4
View File
@@ -36,7 +36,7 @@
const path_to_root = "";
const default_light_theme = "light";
const default_dark_theme = "navy";
window.path_to_searchindex_js = "searchindex-756a6899.js";
window.path_to_searchindex_js = "searchindex-82723808.js";
</script>
<!-- Start loading toc.js asap -->
<script src="toc-6f3f265e.js"></script>
@@ -235,14 +235,14 @@ Please find our <a href="https://github.com/paritytech/revive/releases">binary r
<li><code>s</code>: Optimize for code size.</li>
<li><code>z</code>: Aggressively optimize for code size.</li>
</ul>
<p>By default, <code>-O3</code> is applied.</p>
<p>By default, <code>-Oz</code> is applied.</p>
<h3 id="stack-size"><a class="header" href="#stack-size">Stack size</a></h3>
<pre><code class="language-bash">--stack-size &lt;STACK_SIZE&gt;
</code></pre>
<p>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.</p>
<p>You are incentivized to keep this value as small as possible:</p>
<ol>
<li>Increasing the stack size will increase startup costs.</li>
<li>Increasing the stack size will increase gas costs due to increased startup costs.</li>
<li>The stack size contributes to the total memory size a contract can use, which includes the contracts code size.</li>
</ol>
<p>Default value: 32768</p>
@@ -539,7 +539,7 @@ Thus, <code>revive</code> is able to supply the expected code hash and construct
<p>Unlike with the EVM, where heap memory usage is gas metered, our heap size is static (the size is user controllable via a setting flag). The compiler emits bound checks to prevent overflows.</p>
<h2 id="the-llvm-dependency"><a class="header" href="#the-llvm-dependency">The LLVM dependency</a></h2>
<p>LLVM is a special non Rust dependency. We interface its builder interface via the <a href="https://crates.io/crates/inkwell">inkwell</a> wrapper crate.</p>
<p>We use upstream LLVM, but release and use our custom builds. We require the compiler builtins specifically built for the PVM rv64e target and always leave assertions on. Furthermore, we need cross builds because <code>resolc</code> itself targets emscripten and musl. The <a href="https://crates.io/crates/revive-llvm-builder">revive-llvm-builer</a> functions as a cross-platform build script and is used to build and release the LLVM dependency.</p>
<p>We use upstream LLVM, but release and use our custom builds. We require the compiler builtins specifically built for the PVM <code>rv64emacb</code> target and always leave assertions on. Furthermore, we need cross builds because <code>resolc</code> itself targets emscripten and musl. The <a href="https://crates.io/crates/revive-llvm-builder">revive-llvm-builer</a> functions as a cross-platform build script and is used to build and release the LLVM dependency.</p>
<p>We also maintain the <a href="https://crates.io/crates/lld-sys">lld-sys crate</a> for interfacing with <code>LLD</code>. The LLVM linker is used during the compilation process, but we dont want to distribute another binary.</p>
<h2 id="custom-optimizations"><a class="header" href="#custom-optimizations">Custom optimizations</a></h2>
<p>At the moment, no significant custom optimizations are implemented. Thus, we are missing some optimization opportunities that neither <code>solc</code> nor LLVM can realize (due to their lack of domain specific knowledge about the semantics of our target environment). Furthermore, <code>solc</code> optimizes for EVM gas and a target machine orthogonal to our target (BE 256-bit stack machine EVM vs. 64-bit LE RISC architecture PVM). We have started working on an additional IR layer between Yul and LLVM to capture missed optimization opportunities, though.</p>
+1 -1
View File
@@ -35,7 +35,7 @@
const path_to_root = "";
const default_light_theme = "light";
const default_dark_theme = "navy";
window.path_to_searchindex_js = "searchindex-756a6899.js";
window.path_to_searchindex_js = "searchindex-82723808.js";
</script>
<!-- Start loading toc.js asap -->
<script src="toc-6f3f265e.js"></script>
+1 -1
View File
@@ -35,7 +35,7 @@
const path_to_root = "";
const default_light_theme = "light";
const default_dark_theme = "navy";
window.path_to_searchindex_js = "searchindex-756a6899.js";
window.path_to_searchindex_js = "searchindex-82723808.js";
</script>
<!-- Start loading toc.js asap -->
<script src="toc-6f3f265e.js"></script>
+1 -1
View File
@@ -437,7 +437,7 @@ window.search = window.search || {};
if (yes) {
loadSearchScript(
window.path_to_searchindex_js ||
path_to_root + 'searchindex-756a6899.js',
path_to_root + 'searchindex-82723808.js',
'mdbook-search-index');
search_wrap.classList.remove('hidden');
searchicon.setAttribute('aria-expanded', 'true');
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -35,7 +35,7 @@
const path_to_root = "";
const default_light_theme = "light";
const default_dark_theme = "navy";
window.path_to_searchindex_js = "searchindex-756a6899.js";
window.path_to_searchindex_js = "searchindex-82723808.js";
</script>
<!-- Start loading toc.js asap -->
<script src="toc-6f3f265e.js"></script>
+3 -3
View File
@@ -35,7 +35,7 @@
const path_to_root = "../";
const default_light_theme = "light";
const default_dark_theme = "navy";
window.path_to_searchindex_js = "../searchindex-756a6899.js";
window.path_to_searchindex_js = "../searchindex-82723808.js";
</script>
<!-- Start loading toc.js asap -->
<script src="../toc-6f3f265e.js"></script>
@@ -193,14 +193,14 @@
<li><code>s</code>: Optimize for code size.</li>
<li><code>z</code>: Aggressively optimize for code size.</li>
</ul>
<p>By default, <code>-O3</code> is applied.</p>
<p>By default, <code>-Oz</code> is applied.</p>
<h3 id="stack-size"><a class="header" href="#stack-size">Stack size</a></h3>
<pre><code class="language-bash">--stack-size &lt;STACK_SIZE&gt;
</code></pre>
<p>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.</p>
<p>You are incentivized to keep this value as small as possible:</p>
<ol>
<li>Increasing the stack size will increase startup costs.</li>
<li>Increasing the stack size will increase gas costs due to increased startup costs.</li>
<li>The stack size contributes to the total memory size a contract can use, which includes the contracts code size.</li>
</ol>
<p>Default value: 32768</p>
+1 -1
View File
@@ -35,7 +35,7 @@
const path_to_root = "../";
const default_light_theme = "light";
const default_dark_theme = "navy";
window.path_to_searchindex_js = "../searchindex-756a6899.js";
window.path_to_searchindex_js = "../searchindex-82723808.js";
</script>
<!-- Start loading toc.js asap -->
<script src="../toc-6f3f265e.js"></script>
+1 -1
View File
@@ -35,7 +35,7 @@
const path_to_root = "../";
const default_light_theme = "light";
const default_dark_theme = "navy";
window.path_to_searchindex_js = "../searchindex-756a6899.js";
window.path_to_searchindex_js = "../searchindex-82723808.js";
</script>
<!-- Start loading toc.js asap -->
<script src="../toc-6f3f265e.js"></script>
+1 -1
View File
@@ -35,7 +35,7 @@
const path_to_root = "../";
const default_light_theme = "light";
const default_dark_theme = "navy";
window.path_to_searchindex_js = "../searchindex-756a6899.js";
window.path_to_searchindex_js = "../searchindex-82723808.js";
</script>
<!-- Start loading toc.js asap -->
<script src="../toc-6f3f265e.js"></script>
+1 -1
View File
@@ -35,7 +35,7 @@
const path_to_root = "../";
const default_light_theme = "light";
const default_dark_theme = "navy";
window.path_to_searchindex_js = "../searchindex-756a6899.js";
window.path_to_searchindex_js = "../searchindex-82723808.js";
</script>
<!-- Start loading toc.js asap -->
<script src="../toc-6f3f265e.js"></script>
+1 -1
View File
@@ -35,7 +35,7 @@
const path_to_root = "../";
const default_light_theme = "light";
const default_dark_theme = "navy";
window.path_to_searchindex_js = "../searchindex-756a6899.js";
window.path_to_searchindex_js = "../searchindex-82723808.js";
</script>
<!-- Start loading toc.js asap -->
<script src="../toc-6f3f265e.js"></script>
+1 -1
View File
@@ -35,7 +35,7 @@
const path_to_root = "../";
const default_light_theme = "light";
const default_dark_theme = "navy";
window.path_to_searchindex_js = "../searchindex-756a6899.js";
window.path_to_searchindex_js = "../searchindex-82723808.js";
</script>
<!-- Start loading toc.js asap -->
<script src="../toc-6f3f265e.js"></script>
+1 -1
View File
@@ -35,7 +35,7 @@
const path_to_root = "";
const default_light_theme = "light";
const default_dark_theme = "navy";
window.path_to_searchindex_js = "searchindex-756a6899.js";
window.path_to_searchindex_js = "searchindex-82723808.js";
</script>
<!-- Start loading toc.js asap -->
<script src="toc-6f3f265e.js"></script>
+1 -1
View File
@@ -3,7 +3,7 @@ const path = require("path");
const { minify } = require("terser");
const SOLJSON_URI =
"https://binaries.soliditylang.org/wasm/soljson-v0.8.30+commit.73712a01.js";
"https://binaries.soliditylang.org/wasm/soljson-v0.8.31+commit.fd3a2265.js";
const RESOLC_WASM_URI =
process.env.RELEASE_RESOLC_WASM_URI || "http://127.0.0.1:8080/resolc.wasm";
const RESOLC_WASM_TARGET_DIR = path.join(
+1 -1
View File
@@ -2,7 +2,7 @@
"name": "revive",
"private": true,
"dependencies": {
"solc": ">=0.8.0 <=0.8.30"
"solc": ">=0.8.0 <=0.8.31"
},
"scripts": {
"example:web": "http-server ./examples/web/",
+1 -1
View File
@@ -34,6 +34,6 @@
"commander": "^13.1.0",
"package-json": "^10.0.1",
"resolve-pkg": "^2.0.0",
"solc": ">=0.8.0 <=0.8.30"
"solc": ">=0.8.0 <=0.8.31"
}
}
+7 -2
View File
@@ -18,7 +18,7 @@
"js/emscripten": {
"name": "revive",
"dependencies": {
"solc": ">=0.8.0 <=0.8.30"
"solc": ">=0.8.0 <=0.8.31"
},
"devDependencies": {
"@playwright/test": "^1.49.1",
@@ -38,7 +38,7 @@
"commander": "^13.1.0",
"package-json": "^10.0.1",
"resolve-pkg": "^2.0.0",
"solc": ">=0.8.0 <=0.8.30"
"solc": ">=0.8.0 <=0.8.31"
},
"bin": {
"resolc": "dist/bin.js"
@@ -1585,6 +1585,7 @@
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.32.1.tgz",
"integrity": "sha512-LKMrmwCPoLhM45Z00O1ulb6jwyVr2kr3XJp+G+tSEZcbauNnScewcQwtJqXDhXeYPDEjZ8C1SjXm015CirEmGg==",
"license": "MIT",
"peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "8.32.1",
"@typescript-eslint/types": "8.32.1",
@@ -1752,6 +1753,7 @@
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz",
"integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==",
"license": "MIT",
"peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -2361,6 +2363,7 @@
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.27.0.tgz",
"integrity": "sha512-ixRawFQuMB9DZ7fjU3iGGganFDp3+45bPOdaRurcFHSXO1e/sYwUX/FtQZpLZJR6SjMoJH8hR2pPEAfDyCoU2Q==",
"license": "MIT",
"peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.12.1",
@@ -2519,6 +2522,7 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"@ethersproject/abi": "5.8.0",
"@ethersproject/abstract-provider": "5.8.0",
@@ -4393,6 +4397,7 @@
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
"license": "Apache-2.0",
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"