Compare commits

...

8 Commits

Author SHA1 Message Date
xermicus 5235c4185a Merge branch 'main' into cl/call_gas 2025-12-17 11:32:05 +01:00
Cyrill Leutwiler d08785b1ae the call gas clipping integration test
Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
2025-12-17 11:25:54 +01:00
Cyrill Leutwiler 2062bf8711 the injected custom warning message
Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
2025-12-17 10:34:33 +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
Cyrill Leutwiler 8a908374d4 the EVM call gas syscalls
Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
2025-12-16 11:33:55 +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
25 changed files with 1266 additions and 1273 deletions
+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
"# "#