mirror of
https://github.com/pezkuwichain/revive.git
synced 2026-06-14 22:41:07 +00:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5235c4185a | |||
| d08785b1ae | |||
| 2062bf8711 | |||
| 25ee4eef5a | |||
| 8a908374d4 | |||
| 9446132608 | |||
| 91bd1b0b4e | |||
| e568a924ae |
@@ -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
|
||||
|
||||
|
||||
+5
-1
@@ -4,7 +4,7 @@
|
||||
|
||||
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/
|
||||
@@ -14,10 +14,14 @@ Supported `polkadot-sdk` rev: `2509.0.0`
|
||||
### 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
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -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]
|
||||
|
||||
@@ -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,10 +1,10 @@
|
||||
{
|
||||
"Baseline": 911,
|
||||
"Computation": 2293,
|
||||
"DivisionArithmetics": 14353,
|
||||
"ERC20": 16936,
|
||||
"Computation": 2337,
|
||||
"DivisionArithmetics": 14488,
|
||||
"ERC20": 17041,
|
||||
"Events": 1672,
|
||||
"FibonacciIterative": 1454,
|
||||
"Flipper": 2083,
|
||||
"SHA1": 7727
|
||||
"Flipper": 2106,
|
||||
"SHA1": 7814
|
||||
}
|
||||
@@ -26,6 +26,6 @@ pragma solidity ^0.8;
|
||||
|
||||
contract BaseFee {
|
||||
constructor() payable {
|
||||
assert(block.basefee == 0);
|
||||
assert(block.basefee > 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,6 @@ pragma solidity ^0.8;
|
||||
|
||||
contract GasLeft {
|
||||
constructor() payable {
|
||||
assert(gasleft() > gasleft());
|
||||
assert(gasleft() > 0 && gasleft() < 0xffffffffffffffff);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,6 @@ pragma solidity ^0.8;
|
||||
|
||||
contract GasLimit {
|
||||
constructor() payable {
|
||||
assert(block.gaslimit == 2000000000000);
|
||||
assert(block.gaslimit > 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,6 @@ pragma solidity ^0.8;
|
||||
|
||||
contract GasPrice {
|
||||
constructor() payable {
|
||||
assert(tx.gasprice == 1000);
|
||||
assert(tx.gasprice > 1000);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -66,6 +66,7 @@ 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 {
|
||||
@@ -451,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);
|
||||
@@ -669,3 +626,86 @@ fn sbrk_bounds_checks() {
|
||||
"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_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);
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
|
||||
@@ -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,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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
"#
|
||||
|
||||
Reference in New Issue
Block a user