mirror of
https://github.com/pezkuwichain/revive.git
synced 2026-04-29 22:57:56 +00:00
3204b61ea0
Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
128 lines
4.4 KiB
Rust
128 lines
4.4 KiB
Rust
//! Translates the arithmetic operations.
|
|
|
|
use inkwell::values::BasicValue;
|
|
|
|
use crate::polkavm::context::runtime::RuntimeFunction;
|
|
use crate::polkavm::context::Context;
|
|
use crate::polkavm::Dependency;
|
|
use crate::polkavm::WriteLLVM;
|
|
|
|
const SOLIDITY_TRANSFER_GAS_STIPEND_THRESHOLD: u64 = 2300;
|
|
|
|
/// The Solidity `address.transfer` and `address.send` call detection heuristic.
|
|
///
|
|
/// # Why
|
|
/// This heuristic is an additional security feature to guard against re-entrancy attacks
|
|
/// in case contract authors violate Solidity best practices and use `address.transfer` or
|
|
/// `address.send`.
|
|
/// While contract authors are supposed to never use `address.transfer` or `address.send`,
|
|
/// for a small cost we can be extra defensive about it.
|
|
///
|
|
/// # How
|
|
/// The gas stipend emitted by solc for `transfer` and `send` is not static, thus:
|
|
/// - Dynamically allow re-entrancy only for calls considered not transfer or send.
|
|
/// - Detected balance transfers will supply 0 deposit limit instead of `u256::MAX`.
|
|
///
|
|
/// Calls are considered transfer or send if:
|
|
/// - (Input length | Output lenght) == 0;
|
|
/// - Gas <= 2300;
|
|
///
|
|
/// # Arguments:
|
|
/// - The deposit value pointer.
|
|
/// - The gas value.
|
|
/// - `input_length | output_length`.
|
|
///
|
|
///
|
|
/// # Returns:
|
|
/// The call flags xlen `IntValue`
|
|
pub struct CallReentrancyProtector;
|
|
|
|
impl<D> RuntimeFunction<D> for CallReentrancyProtector
|
|
where
|
|
D: Dependency + Clone,
|
|
{
|
|
const NAME: &'static str = "__revive_call_reentrancy_protector";
|
|
|
|
fn r#type<'ctx>(context: &Context<'ctx, D>) -> inkwell::types::FunctionType<'ctx> {
|
|
context.xlen_type().fn_type(
|
|
&[
|
|
context.llvm().ptr_type(Default::default()).into(),
|
|
context.word_type().into(),
|
|
context.xlen_type().into(),
|
|
],
|
|
false,
|
|
)
|
|
}
|
|
|
|
fn emit_body<'ctx>(
|
|
&self,
|
|
context: &mut Context<'ctx, D>,
|
|
) -> anyhow::Result<Option<inkwell::values::BasicValueEnum<'ctx>>> {
|
|
let deposit_pointer = Self::paramater(context, 0).into_pointer_value();
|
|
let gas = Self::paramater(context, 1).into_int_value();
|
|
let input_length_or_output_length = Self::paramater(context, 2).into_int_value();
|
|
|
|
// Branch-free SSA implementation: First derive the heuristic boolean (int1) value.
|
|
let is_no_input_no_output = context.builder().build_int_compare(
|
|
inkwell::IntPredicate::EQ,
|
|
context.xlen_type().const_zero(),
|
|
input_length_or_output_length,
|
|
"is_no_input_no_output",
|
|
)?;
|
|
let gas_stipend = context
|
|
.word_type()
|
|
.const_int(SOLIDITY_TRANSFER_GAS_STIPEND_THRESHOLD, false);
|
|
let is_gas_stipend_for_transfer_or_send = context.builder().build_int_compare(
|
|
inkwell::IntPredicate::ULE,
|
|
gas,
|
|
gas_stipend,
|
|
"is_gas_stipend_for_transfer_or_send",
|
|
)?;
|
|
let is_balance_transfer = context.builder().build_and(
|
|
is_no_input_no_output,
|
|
is_gas_stipend_for_transfer_or_send,
|
|
"is_balance_transfer",
|
|
)?;
|
|
let is_regular_call = context
|
|
.builder()
|
|
.build_not(is_balance_transfer, "is_balance_transfer_inverted")?;
|
|
|
|
// Call flag: Left shift the heuristic boolean value.
|
|
let is_regular_call_xlen = context.builder().build_int_z_extend(
|
|
is_regular_call,
|
|
context.xlen_type(),
|
|
"is_balance_transfer_xlen",
|
|
)?;
|
|
let call_flags = context.builder().build_left_shift(
|
|
is_regular_call_xlen,
|
|
context.xlen_type().const_int(3, false),
|
|
"flags",
|
|
)?;
|
|
|
|
// Deposit limit value: Sign-extended the heuristic boolean value.
|
|
let deposit_limit_value = context.builder().build_int_s_extend(
|
|
is_regular_call,
|
|
context.word_type(),
|
|
"deposit_limit_value",
|
|
)?;
|
|
context
|
|
.builder()
|
|
.build_store(deposit_pointer, deposit_limit_value)?;
|
|
|
|
Ok(Some(call_flags.into()))
|
|
}
|
|
}
|
|
|
|
impl<D> WriteLLVM<D> for CallReentrancyProtector
|
|
where
|
|
D: Dependency + Clone,
|
|
{
|
|
fn declare(&mut self, context: &mut Context<D>) -> anyhow::Result<()> {
|
|
<Self as RuntimeFunction<_>>::declare(self, context)
|
|
}
|
|
|
|
fn into_llvm(self, context: &mut Context<D>) -> anyhow::Result<()> {
|
|
<Self as RuntimeFunction<_>>::emit(&self, context)
|
|
}
|
|
}
|