Compare commits

...

10 Commits

Author SHA1 Message Date
kvp 1f6264e155 Restore llvm commit 2025-12-18 13:16:30 -05:00
kvp 92e9b1365d remove shallow update when initializing llvm submodule 2025-12-18 12:16:45 -05:00
kvp 541a0560e7 Initialize LLVM submodule first 2025-12-18 12:07:28 -05:00
kvp 89462d6da3 Extra logging 2025-12-18 11:47:59 -05:00
kvp b4728d5c5e Infer full LLVM release version from git's tags
The helps to remove special casing from actions and will make future
LLVM updates smoother.
2025-12-18 11:43:40 -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
26 changed files with 1280 additions and 1289 deletions
+14 -16
View File
@@ -20,29 +20,27 @@ runs:
shell: bash shell: bash
run: | run: |
if [ -z "${{ inputs.version }}" ]; then if [ -z "${{ inputs.version }}" ]; then
# Extract branch from .gitmodules (e.g., "release/18.x") # Get the full version from the LLVM submodule.
BRANCH=$(git config -f .gitmodules submodule.llvm.branch) git submodule update --init --recursive
if [ -n "$BRANCH" ]; then cd llvm
# Extract version from branch name (e.g., "18.x" from "release/18.x") # Try to get the most recent tag (e.g., "llvmorg-18.1.8")
VERSION_PREFIX=$(echo "$BRANCH" | sed 's|release/||' | sed 's|\.x$||') LLVM_TAG=$(git describe --tags --abbrev=0 2>/dev/null)
echo "Detected LLVM version prefix from submodule branch: $VERSION_PREFIX" if [ -n "$LLVM_TAG" ]; then
echo "Detected LLVM version from submodule: $LLVM_TAG"
# Special case: pin LLVM 18 to specific version 18.1.8 # Convert "llvmorg-x.y.z" to "llvm-x.y.z"
if [ "$VERSION_PREFIX" = "18" ]; then LLVM_VERSION=$(echo "$LLVM_TAG" | sed 's/^llvmorg-/llvm-/')
echo "Using pinned version for LLVM 18: llvm-18.1.8" echo "Detected LLVM version from submodule: $LLVM_VERSION"
echo "version_prefix=llvm-18.1.8" >> $GITHUB_OUTPUT echo "version_prefix=$LLVM_VERSION" >> $GITHUB_OUTPUT
else
echo "version_prefix=llvm-$VERSION_PREFIX" >> $GITHUB_OUTPUT
fi
else 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 echo "version_prefix=" >> $GITHUB_OUTPUT
fi fi
cd ..
else else
echo "Using explicitly provided version: ${{ inputs.version }}" echo "Using explicitly provided version: ${{ inputs.version }}"
echo "version_prefix=${{ inputs.version }}" >> $GITHUB_OUTPUT echo "version_prefix=${{ inputs.version }}" >> $GITHUB_OUTPUT
fi fi
- name: find asset - name: find asset
id: find id: find
uses: actions/github-script@v7 uses: actions/github-script@v7
+4 -1
View File
@@ -25,9 +25,12 @@ jobs:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with: { fetch-depth: 0 } with: { fetch-depth: 0 }
- name: Install mdBook - name: Install and test the mdBook
run: make test-book run: make test-book
- name: Build book
run: mdbook build book
- name: Build book to tmp - name: Build book to tmp
run: mdbook build book -d docs-tmp run: mdbook build book -d docs-tmp
+5 -1
View File
@@ -4,7 +4,7 @@
This is a development pre-release. This is a development pre-release.
Supported `polkadot-sdk` rev: `2509.0.0` Supported `polkadot-sdk` rev: `unstable2507`
### Added ### Added
- The comprehensive revive compiler book documentation page: https://paritytech.github.io/revive/ - The comprehensive revive compiler book documentation page: https://paritytech.github.io/revive/
@@ -14,10 +14,14 @@ Supported `polkadot-sdk` rev: `2509.0.0`
### Changed ### Changed
- Instruct the LLVM backend and linker to `--relax` (may lead to smaller contract code size). - 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. - 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: ### Fixed:
- The missing `STOP` instruction at the end of `code` blocks. - The missing `STOP` instruction at the end of `code` blocks.
- The missing bounds check in the internal sbrk implementation. - The missing bounds check in the internal sbrk implementation.
- The call gas is no longer ignored.
## v0.5.0 ## 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 # polkadot-sdk and friends
codec = { version = "3.7.5", default-features = false, package = "parity-scale-codec" } codec = { version = "3.7.5", default-features = false, package = "parity-scale-codec" }
scale-info = { version = "2.11.6", default-features = false } scale-info = { version = "2.11.6", default-features = false }
polkadot-sdk = { version = "2509.0.0" } polkadot-sdk = { version = "=2507.4.0" }
# llvm # llvm
[workspace.dependencies.inkwell] [workspace.dependencies.inkwell]
-1
View File
@@ -92,7 +92,6 @@ test-llvm-builder:
test-book: test-book:
cargo install mdbook --version 0.5.1 --locked cargo install mdbook --version 0.5.1 --locked
mdbook test book mdbook test book
mdbook build book
bench: install-bin bench: install-bin
cargo criterion --all --all-features --message-format=json \ cargo criterion --all --all-features --message-format=json \
+5 -5
View File
@@ -1,10 +1,10 @@
{ {
"Baseline": 911, "Baseline": 911,
"Computation": 2293, "Computation": 2337,
"DivisionArithmetics": 14353, "DivisionArithmetics": 14488,
"ERC20": 16936, "ERC20": 17041,
"Events": 1672, "Events": 1672,
"FibonacciIterative": 1454, "FibonacciIterative": 1454,
"Flipper": 2083, "Flipper": 2106,
"SHA1": 7727 "SHA1": 7814
} }
+1 -1
View File
@@ -26,6 +26,6 @@ pragma solidity ^0.8;
contract BaseFee { contract BaseFee {
constructor() payable { 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));
}
}
-1
View File
@@ -26,7 +26,6 @@ pragma solidity ^0.8;
contract GasLeft { contract GasLeft {
constructor() payable { constructor() payable {
assert(gasleft() > gasleft());
assert(gasleft() > 0 && gasleft() < 0xffffffffffffffff); assert(gasleft() > 0 && gasleft() < 0xffffffffffffffff);
} }
} }
+1 -1
View File
@@ -26,6 +26,6 @@ pragma solidity ^0.8;
contract GasLimit { contract GasLimit {
constructor() payable { constructor() payable {
assert(block.gaslimit == 2000000000000); assert(block.gaslimit > 0);
} }
} }
+1 -1
View File
@@ -26,6 +26,6 @@ pragma solidity ^0.8;
contract GasPrice { contract GasPrice {
constructor() payable { constructor() payable {
assert(tx.gasprice == 1000); assert(tx.gasprice > 1000);
} }
} }
+4 -4
View File
@@ -20,7 +20,7 @@ pragma solidity ^0.8.28;
"dest": { "dest": {
"Instantiated": 0 "Instantiated": 0
}, },
"data": "e2179b8e" "data": "0be0e4a60000000000000000000000000000000000000000000000000000000000000000"
} }
} }
] ]
@@ -29,12 +29,12 @@ pragma solidity ^0.8.28;
contract MLoad { contract MLoad {
constructor() payable { 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 { assembly {
m := mload(0) m := mload(_offset)
} }
} }
} }
-15
View File
@@ -23,11 +23,6 @@ pragma solidity ^0.8;
"data": "1c8d16b30000000000000000000000000303030303030303030303030303030303030303000000000000000000000000000000000000000000000000000000000000000a" "data": "1c8d16b30000000000000000000000000303030303030303030303030303030303030303000000000000000000000000000000000000000000000000000000000000000a"
} }
}, },
{
"VerifyCall": {
"success": true
}
},
{ {
"Call": { "Call": {
"dest": { "dest": {
@@ -36,11 +31,6 @@ pragma solidity ^0.8;
"data": "fb9e8d050000000000000000000000000000000000000000000000000000000000000001" "data": "fb9e8d050000000000000000000000000000000000000000000000000000000000000001"
} }
}, },
{
"VerifyCall": {
"success": false
}
},
{ {
"Call": { "Call": {
"dest": { "dest": {
@@ -48,11 +38,6 @@ pragma solidity ^0.8;
}, },
"data": "fb9e8d050000000000000000000000000000000000000000000000000000000000000000" "data": "fb9e8d050000000000000000000000000000000000000000000000000000000000000000"
} }
},
{
"VerifyCall": {
"success": false
}
} }
] ]
} }
-15
View File
@@ -23,11 +23,6 @@ pragma solidity ^0.8;
"data": "1c8d16b30000000000000000000000000303030303030303030303030303030303030303000000000000000000000000000000000000000000000000000000000000000a" "data": "1c8d16b30000000000000000000000000303030303030303030303030303030303030303000000000000000000000000000000000000000000000000000000000000000a"
} }
}, },
{
"VerifyCall": {
"success": true
}
},
{ {
"Call": { "Call": {
"dest": { "dest": {
@@ -36,11 +31,6 @@ pragma solidity ^0.8;
"data": "fb9e8d050000000000000000000000000000000000000000000000000000000000000001" "data": "fb9e8d050000000000000000000000000000000000000000000000000000000000000001"
} }
}, },
{
"VerifyCall": {
"success": false
}
},
{ {
"Call": { "Call": {
"dest": { "dest": {
@@ -48,11 +38,6 @@ pragma solidity ^0.8;
}, },
"data": "fb9e8d050000000000000000000000000000000000000000000000000000000000000000" "data": "fb9e8d050000000000000000000000000000000000000000000000000000000000000000"
} }
},
{
"VerifyCall": {
"success": false
}
} }
] ]
} }
+9
View File
@@ -234,6 +234,15 @@ sol!(
); );
case!("MCopy.sol", MCopy, memcpyCall, memcpy, payload: Bytes); 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!( sol!(
contract Call { contract Call {
function value_transfer(address payable destination) public payable; function value_transfer(address payable destination) public payable;
+84 -44
View File
@@ -66,6 +66,7 @@ test_spec!(add_mod_mul_mod, "AddModMulModTester", "AddModMulMod.sol");
test_spec!(memory_bounds, "MemoryBounds", "MemoryBounds.sol"); test_spec!(memory_bounds, "MemoryBounds", "MemoryBounds.sol");
test_spec!(selfdestruct, "Selfdestruct", "Selfdestruct.sol"); test_spec!(selfdestruct, "Selfdestruct", "Selfdestruct.sol");
test_spec!(clz, "CountLeadingZeros", "CountLeadingZeros.sol"); test_spec!(clz, "CountLeadingZeros", "CountLeadingZeros.sol");
test_spec!(call_gas, "CallGas", "CallGas.sol");
fn instantiate(path: &str, contract: &str) -> Vec<SpecsAction> { fn instantiate(path: &str, contract: &str) -> Vec<SpecsAction> {
vec![Instantiate { vec![Instantiate {
@@ -451,50 +452,6 @@ fn ext_code_size() {
.run(); .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] #[test]
fn create2_salt() { fn create2_salt() {
let salt = U256::from(777); let salt = U256::from(777);
@@ -669,3 +626,86 @@ fn sbrk_bounds_checks() {
"not seeing a trap means the contract did not catch the OOB" "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);
}
@@ -51,11 +51,11 @@ impl RuntimeFunction for WordToPointer {
)?; )?;
let block_continue = context.append_basic_block("offset_pointer_ok"); let block_continue = context.append_basic_block("offset_pointer_ok");
let block_trap = context.append_basic_block("offset_pointer_overflow"); let block_invalid = context.append_basic_block("offset_pointer_overflow");
context.build_conditional_branch(is_overflow, block_trap, block_continue)?; context.build_conditional_branch(is_overflow, block_invalid, block_continue)?;
context.set_basic_block(block_trap); context.set_basic_block(block_invalid);
context.build_call(context.intrinsics().trap, &[], "invalid_trap"); context.build_runtime_call(revive_runtime_api::polkavm_imports::INVALID, &[]);
context.build_unreachable(); context.build_unreachable();
context.set_basic_block(block_continue); context.set_basic_block(block_continue);
+27 -122
View File
@@ -4,9 +4,8 @@ use inkwell::values::BasicValue;
use crate::polkavm::context::Context; use crate::polkavm::context::Context;
const STATIC_CALL_FLAG: u32 = 0b0001_0000; const STATIC_CALL_FLAG: u64 = 0b0001_0000;
const REENTRANT_CALL_FLAG: u32 = 0b0000_1000; const REENTRANT_CALL_FLAG: u64 = 0b0000_1000;
const SOLIDITY_TRANSFER_GAS_STIPEND_THRESHOLD: u64 = 2300;
/// Translates a contract call. /// Translates a contract call.
pub fn call<'ctx>( 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"); let output_length_pointer = context.build_alloca_at_entry(context.xlen_type(), "output_length");
context.build_store(output_length_pointer, output_length)?; context.build_store(output_length_pointer, output_length)?;
let (flags, deposit_limit_value) = if static_call { let flags = if static_call {
let flags = REENTRANT_CALL_FLAG | STATIC_CALL_FLAG; REENTRANT_CALL_FLAG | STATIC_CALL_FLAG
(
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)? 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( let input_data = revive_runtime_api::calling_convention::pack_hi_lo_reg(
context.builder(), context.builder(),
context.llvm(), context.llvm(),
@@ -85,10 +63,10 @@ pub fn call<'ctx>(
.build_runtime_call( .build_runtime_call(
name, name,
&[ &[
flags_and_callee.into(), context.xlen_type().const_int(flags, false).into(),
context.register_type().const_all_ones().into(), address_pointer.to_int(context).into(),
context.register_type().const_all_ones().into(), value_pointer.to_int(context).into(),
deposit_and_value.into(), clip_call_gas(context, gas)?,
input_data.into(), input_data.into(),
output_data.into(), output_data.into(),
], ],
@@ -111,7 +89,7 @@ pub fn call<'ctx>(
pub fn delegate_call<'ctx>( pub fn delegate_call<'ctx>(
context: &mut Context<'ctx>, context: &mut Context<'ctx>,
_gas: inkwell::values::IntValue<'ctx>, gas: inkwell::values::IntValue<'ctx>,
address: inkwell::values::IntValue<'ctx>, address: inkwell::values::IntValue<'ctx>,
input_offset: inkwell::values::IntValue<'ctx>, input_offset: inkwell::values::IntValue<'ctx>,
input_length: 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"); let output_length_pointer = context.build_alloca_at_entry(context.xlen_type(), "output_length");
context.build_store(output_length_pointer, 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( let input_data = revive_runtime_api::calling_convention::pack_hi_lo_reg(
context.builder(), context.builder(),
context.llvm(), context.llvm(),
@@ -164,10 +130,9 @@ pub fn delegate_call<'ctx>(
.build_runtime_call( .build_runtime_call(
name, name,
&[ &[
flags_and_callee.into(), context.xlen_type().const_int(0u64, false).into(),
context.register_type().const_all_ones().into(), address_pointer.to_int(context).into(),
context.register_type().const_all_ones().into(), clip_call_gas(context, gas)?,
deposit_pointer.to_int(context).into(),
input_data.into(), input_data.into(),
output_data.into(), output_data.into(),
], ],
@@ -201,82 +166,22 @@ pub fn linker_symbol<'ctx>(
context.build_load_address(context.get_global(path)?.into()) context.build_load_address(context.get_global(path)?.into())
} }
/// The Solidity `address.transfer` and `address.send` call detection heuristic. /// The runtime implements gas as `u64` so we clip the stipend to `u64::MAX`.
/// fn clip_call_gas<'ctx>(
/// # Why context: &Context<'ctx>,
/// 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>,
gas: inkwell::values::IntValue<'ctx>, gas: inkwell::values::IntValue<'ctx>,
input_length: inkwell::values::IntValue<'ctx>, ) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>> {
output_length: inkwell::values::IntValue<'ctx>, let builder = context.builder();
) -> anyhow::Result<(
inkwell::values::IntValue<'ctx>, let clipped = context.register_type().const_all_ones();
inkwell::values::IntValue<'ctx>, let is_overflow = builder.build_int_compare(
)> { inkwell::IntPredicate::UGT,
// 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,
gas, gas,
gas_stipend, builder.build_int_z_extend(clipped, context.word_type(), "gas_clipped")?,
"is_gas_stipend_for_transfer_or_send", "is_gas_overflow",
)?; )?;
let is_balance_transfer = context.builder().build_and( let truncated = builder.build_int_truncate(gas, context.register_type(), "gas_truncated")?;
is_no_input_no_output, let call_gas = builder.build_select(is_overflow, clipped, truncated, "call_gas")?;
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. Ok(call_gas)
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))
} }
@@ -52,12 +52,14 @@ pub fn stop(context: &mut Context) -> anyhow::Result<()> {
/// Translates the `invalid` instruction. /// Translates the `invalid` instruction.
/// Burns all gas using an out-of-bounds memory store, causing a panic. /// Burns all gas using an out-of-bounds memory store, causing a panic.
pub fn invalid(context: &mut Context) -> anyhow::Result<()> { pub fn invalid(context: &mut Context) -> anyhow::Result<()> {
crate::polkavm::evm::memory::store( let invalid_block = context.append_basic_block("explicit_invalid");
context, context.build_unconditional_branch(invalid_block);
context.word_type().const_all_ones(), context.set_basic_block(invalid_block);
context.word_const(0), context.build_runtime_call(revive_runtime_api::polkavm_imports::INVALID, &[]);
)?; context.build_unreachable();
context.build_call(context.intrinsics().trap, &[], "invalid_trap");
context.set_basic_block(context.append_basic_block("dead_code"));
Ok(()) Ok(())
} }
+10 -5
View File
@@ -59,7 +59,7 @@ pub const CHARLIE: H160 = H160([3u8; 20]);
/// Default gas limit /// Default gas limit
pub const GAS_LIMIT: Weight = Weight::from_parts(100_000_000_000_000, 3 * 1024 * 1024 * 1024); pub const GAS_LIMIT: Weight = Weight::from_parts(100_000_000_000_000, 3 * 1024 * 1024 * 1024);
/// Default deposit limit /// 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. /// The native to ETH balance factor.
pub const ETH_RATIO: Balance = 1_000_000; pub const ETH_RATIO: Balance = 1_000_000;
@@ -97,14 +97,19 @@ impl ExtBuilder {
.unwrap(); .unwrap();
let mut ext = sp_io::TestExternalities::new(t); let mut ext = sp_io::TestExternalities::new(t);
let checking_account = Pallet::<Runtime>::account_id();
ext.register_extension(KeystoreExt::new(MemoryKeystore::new())); ext.register_extension(KeystoreExt::new(MemoryKeystore::new()));
ext.execute_with(|| { ext.execute_with(|| {
let _ = <Runtime as Config>::Currency::deposit_creating( let _ = <Runtime as Config>::Currency::deposit_creating(
&Pallet::<Runtime>::account_id(), &checking_account,
<Runtime as Config>::Currency::minimum_balance(), 1_000_000_000_000,
); );
System::set_block_number(1); System::set_block_number(1);
assert_ok!(Pallet::<Runtime>::map_account(RuntimeOrigin::signed(
checking_account
)));
}); });
ext ext
@@ -115,7 +120,7 @@ impl ExtBuilder {
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VerifyCallExpectation { pub struct VerifyCallExpectation {
/// When provided, the expected gas consumed /// When provided, the expected gas consumed
pub gas_consumed: Option<Weight>, pub gas_consumed: Option<u128>,
/// When provided, the expected output /// When provided, the expected output
#[serde(default, with = "hex")] #[serde(default, with = "hex")]
pub output: OptionalHex<Vec<u8>>, pub output: OptionalHex<Vec<u8>>,
@@ -238,7 +243,7 @@ impl CallResult {
} }
/// Get the gas consumed by the call /// Get the gas consumed by the call
fn gas_consumed(&self) -> Weight { fn gas_consumed(&self) -> u128 {
match self { match self {
Self::Exec { result, .. } => result.gas_consumed, Self::Exec { result, .. } => result.gas_consumed,
Self::Instantiate { 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 pallet_revive::AccountId32Mapper;
use polkadot_sdk::*; use polkadot_sdk::*;
use polkadot_sdk::{ use polkadot_sdk::{
polkadot_sdk_frame::{log, runtime::prelude::*}, polkadot_sdk_frame::runtime::prelude::*,
sp_runtime::{AccountId32, Perbill}, sp_runtime::{AccountId32, Perbill},
}; };
@@ -72,6 +72,7 @@ parameter_types! {
#[derive_impl(pallet_revive::config_preludes::TestDefaultConfig)] #[derive_impl(pallet_revive::config_preludes::TestDefaultConfig)]
impl pallet_revive::Config for Runtime { impl pallet_revive::Config for Runtime {
type Balance = Balance;
type Time = Timestamp; type Time = Timestamp;
type Currency = Balances; type Currency = Balances;
type DepositPerByte = DepositPerByte; type DepositPerByte = DepositPerByte;
+14 -9
View File
@@ -1,6 +1,6 @@
use std::{str::FromStr, time::Instant}; 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 serde::{Deserialize, Serialize};
use crate::*; use crate::*;
@@ -210,9 +210,9 @@ impl Default for Specs {
Self { Self {
differential: false, differential: false,
balances: vec![ balances: vec![
(ALICE, 1_000_000_000), (ALICE, 1_000_000_000_000),
(BOB, 1_000_000_000), (BOB, 1_000_000_000_000),
(CHARLIE, 1_000_000_000), (CHARLIE, 1_000_000_000_000),
], ],
actions: Default::default(), actions: Default::default(),
} }
@@ -447,12 +447,14 @@ impl Specs {
let result = Contracts::bare_instantiate( let result = Contracts::bare_instantiate(
origin, origin,
value.into(), value.into(),
gas_limit.unwrap_or(GAS_LIMIT), TransactionLimits::WeightAndDeposit {
storage_deposit_limit.unwrap_or(DEPOSIT_LIMIT).into(), weight_limit: gas_limit.unwrap_or(GAS_LIMIT),
deposit_limit: storage_deposit_limit.unwrap_or(DEPOSIT_LIMIT),
},
code, code,
data, data,
salt.0, salt.0,
pallet_revive::BumpNonce::No, ExecConfig::new_substrate_tx(),
); );
results.push(CallResult::Instantiate { results.push(CallResult::Instantiate {
result, result,
@@ -486,9 +488,12 @@ impl Specs {
RuntimeOrigin::signed(origin.to_account_id(&results)), RuntimeOrigin::signed(origin.to_account_id(&results)),
dest.to_eth_addr(&results), dest.to_eth_addr(&results),
value.into(), value.into(),
gas_limit.unwrap_or(GAS_LIMIT), TransactionLimits::WeightAndDeposit {
storage_deposit_limit.unwrap_or(DEPOSIT_LIMIT).into(), weight_limit: gas_limit.unwrap_or(GAS_LIMIT),
deposit_limit: storage_deposit_limit.unwrap_or(DEPOSIT_LIMIT),
},
data, data,
ExecConfig::new_substrate_tx(),
); );
results.push(CallResult::Exec { results.push(CallResult::Exec {
result, 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(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) 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(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) 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 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"; 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 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"; 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 INSTANTIATE: &str = "instantiate";
pub static INVALID: &str = "consume_all_gas";
pub static NOW: &str = "now"; pub static NOW: &str = "now";
pub static ORIGIN: &str = "origin"; pub static ORIGIN: &str = "origin";
@@ -70,7 +72,7 @@ pub static VALUE_TRANSFERRED: &str = "value_transferred";
/// All imported runtime API symbols. /// All imported runtime API symbols.
/// Useful for configuring common attributes and linkage. /// Useful for configuring common attributes and linkage.
pub static IMPORTS: [&str; 33] = [ pub static IMPORTS: [&str; 34] = [
ADDRESS, ADDRESS,
BALANCE, BALANCE,
BALANCE_OF, BALANCE_OF,
@@ -94,6 +96,7 @@ pub static IMPORTS: [&str; 33] = [
GET_STORAGE, GET_STORAGE,
HASH_KECCAK_256, HASH_KECCAK_256,
INSTANTIATE, INSTANTIATE,
INVALID,
NOW, NOW,
ORIGIN, ORIGIN,
REF_TIME_LEFT, REF_TIME_LEFT,
@@ -113,10 +113,9 @@ impl Error {
let message = r#" let message = r#"
Warning: It looks like you are using '<address payable>.send/transfer(<X>)'. Warning: It looks like you are using '<address payable>.send/transfer(<X>)'.
Using '<address payable>.send/transfer(<X>)' is deprecated and strongly discouraged! 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, The revive runtime uses a heuristic to detect '<address payable>.send/transfer(<X>)' calls and
which disables call re-entrancy and supplies all remaining gas instead of the 2300 gas stipend. the gas stipend used by the runtime is different from the EVM.
However, detection is not guaranteed. You are advised to carefully test this, employ You are advised to carefully test this, employ re-entrancy guards or use the withdrawal pattern instead!
re-entrancy guards or use the withdrawal pattern instead!
Learn more on https://docs.soliditylang.org/en/latest/security-considerations.html#reentrancy 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 and https://docs.soliditylang.org/en/latest/common-patterns.html#withdrawal-from-contracts
"# "#