mirror of
https://github.com/pezkuwichain/revive.git
synced 2026-06-14 18:01:05 +00:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0685df37a1 | |||
| 3204b61ea0 | |||
| 4028f7a985 |
@@ -19,10 +19,22 @@ pub use self::polkavm::context::function::declaration::Declaration as PolkaVMFun
|
||||
pub use self::polkavm::context::function::intrinsics::Intrinsics as PolkaVMIntrinsicFunction;
|
||||
pub use self::polkavm::context::function::llvm_runtime::LLVMRuntime as PolkaVMLLVMRuntime;
|
||||
pub use self::polkavm::context::function::r#return::Return as PolkaVMFunctionReturn;
|
||||
pub use self::polkavm::context::function::runtime::arithmetics::Addition as PolkaVMAdditionFunction;
|
||||
pub use self::polkavm::context::function::runtime::arithmetics::Division as PolkaVMDivisionFunction;
|
||||
pub use self::polkavm::context::function::runtime::arithmetics::Multiplication as PolkaVMMultiplicationFunction;
|
||||
pub use self::polkavm::context::function::runtime::arithmetics::Remainder as PolkaVMRemainderFunction;
|
||||
pub use self::polkavm::context::function::runtime::arithmetics::SignedDivision as PolkaVMSignedDivisionFunction;
|
||||
pub use self::polkavm::context::function::runtime::arithmetics::SignedRemainder as PolkaVMSignedRemainderFunction;
|
||||
pub use self::polkavm::context::function::runtime::arithmetics::Subtraction as PolkaVMSubstractionFunction;
|
||||
pub use self::polkavm::context::function::runtime::bitwise::And as PolkaVMAndFunction;
|
||||
pub use self::polkavm::context::function::runtime::bitwise::Byte as PolkaVMByteFunction;
|
||||
pub use self::polkavm::context::function::runtime::bitwise::Or as PolkaVMOrFunction;
|
||||
pub use self::polkavm::context::function::runtime::bitwise::Sar as PolkaVMSarFunction;
|
||||
pub use self::polkavm::context::function::runtime::bitwise::Shl as PolkaVMShlFunction;
|
||||
pub use self::polkavm::context::function::runtime::bitwise::Shr as PolkaVMShrFunction;
|
||||
pub use self::polkavm::context::function::runtime::bitwise::Xor as PolkaVMXorFunction;
|
||||
pub use self::polkavm::context::function::runtime::call::Call as PolkaVMCall;
|
||||
pub use self::polkavm::context::function::runtime::call::CallReentrancyProtector as PolkaVMCallReentrancyProtector;
|
||||
pub use self::polkavm::context::function::runtime::deploy_code::DeployCode as PolkaVMDeployCodeFunction;
|
||||
pub use self::polkavm::context::function::runtime::entry::Entry as PolkaVMEntryFunction;
|
||||
pub use self::polkavm::context::function::runtime::revive::Exit as PolkaVMExitFunction;
|
||||
|
||||
@@ -7,6 +7,141 @@ use crate::polkavm::context::Context;
|
||||
use crate::polkavm::Dependency;
|
||||
use crate::polkavm::WriteLLVM;
|
||||
|
||||
/// Implements the ADD operator according to the EVM specification.
|
||||
pub struct Addition;
|
||||
|
||||
impl<D> RuntimeFunction<D> for Addition
|
||||
where
|
||||
D: Dependency + Clone,
|
||||
{
|
||||
const NAME: &'static str = "__revive_addition";
|
||||
|
||||
fn r#type<'ctx>(context: &Context<'ctx, D>) -> inkwell::types::FunctionType<'ctx> {
|
||||
context.word_type().fn_type(
|
||||
&[context.word_type().into(), context.word_type().into()],
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
fn emit_body<'ctx>(
|
||||
&self,
|
||||
context: &mut Context<'ctx, D>,
|
||||
) -> anyhow::Result<Option<inkwell::values::BasicValueEnum<'ctx>>> {
|
||||
let operand_1 = Self::paramater(context, 0).into_int_value();
|
||||
let operand_2 = Self::paramater(context, 1).into_int_value();
|
||||
|
||||
Ok(Some(
|
||||
context
|
||||
.builder()
|
||||
.build_int_add(operand_1, operand_2, "ADD")
|
||||
.map(Into::into)?,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl<D> WriteLLVM<D> for Addition
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
/// Implements the SUB operator according to the EVM specification.
|
||||
pub struct Subtraction;
|
||||
|
||||
impl<D> RuntimeFunction<D> for Subtraction
|
||||
where
|
||||
D: Dependency + Clone,
|
||||
{
|
||||
const NAME: &'static str = "__revive_subtraction";
|
||||
|
||||
fn r#type<'ctx>(context: &Context<'ctx, D>) -> inkwell::types::FunctionType<'ctx> {
|
||||
context.word_type().fn_type(
|
||||
&[context.word_type().into(), context.word_type().into()],
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
fn emit_body<'ctx>(
|
||||
&self,
|
||||
context: &mut Context<'ctx, D>,
|
||||
) -> anyhow::Result<Option<inkwell::values::BasicValueEnum<'ctx>>> {
|
||||
let operand_1 = Self::paramater(context, 0).into_int_value();
|
||||
let operand_2 = Self::paramater(context, 1).into_int_value();
|
||||
|
||||
Ok(Some(
|
||||
context
|
||||
.builder()
|
||||
.build_int_sub(operand_1, operand_2, "SUB")
|
||||
.map(Into::into)?,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl<D> WriteLLVM<D> for Subtraction
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
/// Implements the MUL operator according to the EVM specification.
|
||||
pub struct Multiplication;
|
||||
|
||||
impl<D> RuntimeFunction<D> for Multiplication
|
||||
where
|
||||
D: Dependency + Clone,
|
||||
{
|
||||
const NAME: &'static str = "__revive_multiplication";
|
||||
|
||||
fn r#type<'ctx>(context: &Context<'ctx, D>) -> inkwell::types::FunctionType<'ctx> {
|
||||
context.word_type().fn_type(
|
||||
&[context.word_type().into(), context.word_type().into()],
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
fn emit_body<'ctx>(
|
||||
&self,
|
||||
context: &mut Context<'ctx, D>,
|
||||
) -> anyhow::Result<Option<inkwell::values::BasicValueEnum<'ctx>>> {
|
||||
let operand_1 = Self::paramater(context, 0).into_int_value();
|
||||
let operand_2 = Self::paramater(context, 1).into_int_value();
|
||||
|
||||
Ok(Some(
|
||||
context
|
||||
.builder()
|
||||
.build_int_mul(operand_1, operand_2, "MUL")
|
||||
.map(Into::into)?,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl<D> WriteLLVM<D> for Multiplication
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
/// Implements the division operator according to the EVM specification.
|
||||
pub struct Division;
|
||||
|
||||
|
||||
@@ -0,0 +1,494 @@
|
||||
//! 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;
|
||||
|
||||
/// Implements the OR operator according to the EVM specification.
|
||||
pub struct Or;
|
||||
|
||||
impl<D> RuntimeFunction<D> for Or
|
||||
where
|
||||
D: Dependency + Clone,
|
||||
{
|
||||
const NAME: &'static str = "__revive_or";
|
||||
|
||||
fn r#type<'ctx>(context: &Context<'ctx, D>) -> inkwell::types::FunctionType<'ctx> {
|
||||
context.word_type().fn_type(
|
||||
&[context.word_type().into(), context.word_type().into()],
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
fn emit_body<'ctx>(
|
||||
&self,
|
||||
context: &mut Context<'ctx, D>,
|
||||
) -> anyhow::Result<Option<inkwell::values::BasicValueEnum<'ctx>>> {
|
||||
let operand_1 = Self::paramater(context, 0).into_int_value();
|
||||
let operand_2 = Self::paramater(context, 1).into_int_value();
|
||||
|
||||
Ok(Some(
|
||||
context
|
||||
.builder()
|
||||
.build_or(operand_1, operand_2, "OR")
|
||||
.map(Into::into)?,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl<D> WriteLLVM<D> for Or
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
/// Implements the XOR operator according to the EVM specification.
|
||||
pub struct Xor;
|
||||
|
||||
impl<D> RuntimeFunction<D> for Xor
|
||||
where
|
||||
D: Dependency + Clone,
|
||||
{
|
||||
const NAME: &'static str = "__revive_xor";
|
||||
|
||||
fn r#type<'ctx>(context: &Context<'ctx, D>) -> inkwell::types::FunctionType<'ctx> {
|
||||
context.word_type().fn_type(
|
||||
&[context.word_type().into(), context.word_type().into()],
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
fn emit_body<'ctx>(
|
||||
&self,
|
||||
context: &mut Context<'ctx, D>,
|
||||
) -> anyhow::Result<Option<inkwell::values::BasicValueEnum<'ctx>>> {
|
||||
let operand_1 = Self::paramater(context, 0).into_int_value();
|
||||
let operand_2 = Self::paramater(context, 1).into_int_value();
|
||||
|
||||
Ok(Some(
|
||||
context
|
||||
.builder()
|
||||
.build_xor(operand_1, operand_2, "XOR")
|
||||
.map(Into::into)?,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl<D> WriteLLVM<D> for Xor
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
/// Implements the AND operator according to the EVM specification.
|
||||
pub struct And;
|
||||
|
||||
impl<D> RuntimeFunction<D> for And
|
||||
where
|
||||
D: Dependency + Clone,
|
||||
{
|
||||
const NAME: &'static str = "__revive_and";
|
||||
|
||||
fn r#type<'ctx>(context: &Context<'ctx, D>) -> inkwell::types::FunctionType<'ctx> {
|
||||
context.word_type().fn_type(
|
||||
&[context.word_type().into(), context.word_type().into()],
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
fn emit_body<'ctx>(
|
||||
&self,
|
||||
context: &mut Context<'ctx, D>,
|
||||
) -> anyhow::Result<Option<inkwell::values::BasicValueEnum<'ctx>>> {
|
||||
let operand_1 = Self::paramater(context, 0).into_int_value();
|
||||
let operand_2 = Self::paramater(context, 1).into_int_value();
|
||||
|
||||
Ok(Some(
|
||||
context
|
||||
.builder()
|
||||
.build_and(operand_1, operand_2, "AND")
|
||||
.map(Into::into)?,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl<D> WriteLLVM<D> for And
|
||||
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)
|
||||
}
|
||||
}
|
||||
/// Implements the SHL operator according to the EVM specification.
|
||||
pub struct Shl;
|
||||
|
||||
impl<D> RuntimeFunction<D> for Shl
|
||||
where
|
||||
D: Dependency + Clone,
|
||||
{
|
||||
const NAME: &'static str = "__revive_shl";
|
||||
|
||||
fn r#type<'ctx>(context: &Context<'ctx, D>) -> inkwell::types::FunctionType<'ctx> {
|
||||
context.word_type().fn_type(
|
||||
&[context.word_type().into(), context.word_type().into()],
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
fn emit_body<'ctx>(
|
||||
&self,
|
||||
context: &mut Context<'ctx, D>,
|
||||
) -> anyhow::Result<Option<inkwell::values::BasicValueEnum<'ctx>>> {
|
||||
let shift = Self::paramater(context, 0).into_int_value();
|
||||
let value = Self::paramater(context, 1).into_int_value();
|
||||
|
||||
let overflow_block = context.append_basic_block("shift_left_overflow");
|
||||
let non_overflow_block = context.append_basic_block("shift_left_non_overflow");
|
||||
let join_block = context.append_basic_block("shift_left_join");
|
||||
|
||||
let condition_is_overflow = context.builder().build_int_compare(
|
||||
inkwell::IntPredicate::UGT,
|
||||
shift,
|
||||
context.word_const((revive_common::BIT_LENGTH_WORD - 1) as u64),
|
||||
"shift_left_is_overflow",
|
||||
)?;
|
||||
context.build_conditional_branch(
|
||||
condition_is_overflow,
|
||||
overflow_block,
|
||||
non_overflow_block,
|
||||
)?;
|
||||
|
||||
context.set_basic_block(overflow_block);
|
||||
context.build_unconditional_branch(join_block);
|
||||
|
||||
context.set_basic_block(non_overflow_block);
|
||||
let value =
|
||||
context
|
||||
.builder()
|
||||
.build_left_shift(value, shift, "shift_left_non_overflow_result")?;
|
||||
context.build_unconditional_branch(join_block);
|
||||
|
||||
context.set_basic_block(join_block);
|
||||
let result = context
|
||||
.builder()
|
||||
.build_phi(context.word_type(), "shift_left_value")?;
|
||||
result.add_incoming(&[
|
||||
(&value, non_overflow_block),
|
||||
(&context.word_const(0), overflow_block),
|
||||
]);
|
||||
Ok(Some(result.as_basic_value()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<D> WriteLLVM<D> for Shl
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
/// Implements the SHR operator according to the EVM specification.
|
||||
pub struct Shr;
|
||||
|
||||
impl<D> RuntimeFunction<D> for Shr
|
||||
where
|
||||
D: Dependency + Clone,
|
||||
{
|
||||
const NAME: &'static str = "__revive_shr";
|
||||
|
||||
fn r#type<'ctx>(context: &Context<'ctx, D>) -> inkwell::types::FunctionType<'ctx> {
|
||||
context.word_type().fn_type(
|
||||
&[context.word_type().into(), context.word_type().into()],
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
fn emit_body<'ctx>(
|
||||
&self,
|
||||
context: &mut Context<'ctx, D>,
|
||||
) -> anyhow::Result<Option<inkwell::values::BasicValueEnum<'ctx>>> {
|
||||
let shift = Self::paramater(context, 0).into_int_value();
|
||||
let value = Self::paramater(context, 1).into_int_value();
|
||||
|
||||
let overflow_block = context.append_basic_block("shift_right_overflow");
|
||||
let non_overflow_block = context.append_basic_block("shift_right_non_overflow");
|
||||
let join_block = context.append_basic_block("shift_right_join");
|
||||
|
||||
let condition_is_overflow = context.builder().build_int_compare(
|
||||
inkwell::IntPredicate::UGT,
|
||||
shift,
|
||||
context.word_const((revive_common::BIT_LENGTH_WORD - 1) as u64),
|
||||
"shift_right_is_overflow",
|
||||
)?;
|
||||
context.build_conditional_branch(
|
||||
condition_is_overflow,
|
||||
overflow_block,
|
||||
non_overflow_block,
|
||||
)?;
|
||||
|
||||
context.set_basic_block(overflow_block);
|
||||
context.build_unconditional_branch(join_block);
|
||||
|
||||
context.set_basic_block(non_overflow_block);
|
||||
let value = context.builder().build_right_shift(
|
||||
value,
|
||||
shift,
|
||||
false,
|
||||
"shift_right_non_overflow_result",
|
||||
)?;
|
||||
context.build_unconditional_branch(join_block);
|
||||
|
||||
context.set_basic_block(join_block);
|
||||
let result = context
|
||||
.builder()
|
||||
.build_phi(context.word_type(), "shift_right_value")?;
|
||||
result.add_incoming(&[
|
||||
(&value, non_overflow_block),
|
||||
(&context.word_const(0), overflow_block),
|
||||
]);
|
||||
Ok(Some(result.as_basic_value()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<D> WriteLLVM<D> for Shr
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
/// Implements the SAR operator according to the EVM specification.
|
||||
pub struct Sar;
|
||||
|
||||
impl<D> RuntimeFunction<D> for Sar
|
||||
where
|
||||
D: Dependency + Clone,
|
||||
{
|
||||
const NAME: &'static str = "__revive_sar";
|
||||
|
||||
fn r#type<'ctx>(context: &Context<'ctx, D>) -> inkwell::types::FunctionType<'ctx> {
|
||||
context.word_type().fn_type(
|
||||
&[context.word_type().into(), context.word_type().into()],
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
fn emit_body<'ctx>(
|
||||
&self,
|
||||
context: &mut Context<'ctx, D>,
|
||||
) -> anyhow::Result<Option<inkwell::values::BasicValueEnum<'ctx>>> {
|
||||
let shift = Self::paramater(context, 0).into_int_value();
|
||||
let value = Self::paramater(context, 1).into_int_value();
|
||||
|
||||
let overflow_block = context.append_basic_block("shift_right_arithmetic_overflow");
|
||||
let overflow_positive_block =
|
||||
context.append_basic_block("shift_right_arithmetic_overflow_positive");
|
||||
let overflow_negative_block =
|
||||
context.append_basic_block("shift_right_arithmetic_overflow_negative");
|
||||
let non_overflow_block = context.append_basic_block("shift_right_arithmetic_non_overflow");
|
||||
let join_block = context.append_basic_block("shift_right_arithmetic_join");
|
||||
|
||||
let condition_is_overflow = context.builder().build_int_compare(
|
||||
inkwell::IntPredicate::UGT,
|
||||
shift,
|
||||
context.word_const((revive_common::BIT_LENGTH_WORD - 1) as u64),
|
||||
"shift_right_arithmetic_is_overflow",
|
||||
)?;
|
||||
context.build_conditional_branch(
|
||||
condition_is_overflow,
|
||||
overflow_block,
|
||||
non_overflow_block,
|
||||
)?;
|
||||
|
||||
context.set_basic_block(overflow_block);
|
||||
let sign_bit = context.builder().build_right_shift(
|
||||
value,
|
||||
context.word_const((revive_common::BIT_LENGTH_WORD - 1) as u64),
|
||||
false,
|
||||
"shift_right_arithmetic_sign_bit",
|
||||
)?;
|
||||
let condition_is_negative = context.builder().build_int_truncate_or_bit_cast(
|
||||
sign_bit,
|
||||
context.bool_type(),
|
||||
"shift_right_arithmetic_sign_bit_truncated",
|
||||
)?;
|
||||
context.build_conditional_branch(
|
||||
condition_is_negative,
|
||||
overflow_negative_block,
|
||||
overflow_positive_block,
|
||||
)?;
|
||||
|
||||
context.set_basic_block(overflow_positive_block);
|
||||
context.build_unconditional_branch(join_block);
|
||||
|
||||
context.set_basic_block(overflow_negative_block);
|
||||
context.build_unconditional_branch(join_block);
|
||||
|
||||
context.set_basic_block(non_overflow_block);
|
||||
let value = context.builder().build_right_shift(
|
||||
value,
|
||||
shift,
|
||||
true,
|
||||
"shift_right_arithmetic_non_overflow_result",
|
||||
)?;
|
||||
context.build_unconditional_branch(join_block);
|
||||
|
||||
context.set_basic_block(join_block);
|
||||
let result = context
|
||||
.builder()
|
||||
.build_phi(context.word_type(), "shift_arithmetic_right_value")?;
|
||||
result.add_incoming(&[
|
||||
(&value, non_overflow_block),
|
||||
(
|
||||
&context.word_type().const_all_ones(),
|
||||
overflow_negative_block,
|
||||
),
|
||||
(&context.word_const(0), overflow_positive_block),
|
||||
]);
|
||||
Ok(Some(result.as_basic_value()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<D> WriteLLVM<D> for Sar
|
||||
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)
|
||||
}
|
||||
}
|
||||
/// Implements the BYTE operator according to the EVM specification.
|
||||
pub struct Byte;
|
||||
|
||||
impl<D> RuntimeFunction<D> for Byte
|
||||
where
|
||||
D: Dependency + Clone,
|
||||
{
|
||||
const NAME: &'static str = "__revive_byte";
|
||||
|
||||
fn r#type<'ctx>(context: &Context<'ctx, D>) -> inkwell::types::FunctionType<'ctx> {
|
||||
context.word_type().fn_type(
|
||||
&[context.word_type().into(), context.word_type().into()],
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
fn emit_body<'ctx>(
|
||||
&self,
|
||||
context: &mut Context<'ctx, D>,
|
||||
) -> anyhow::Result<Option<inkwell::values::BasicValueEnum<'ctx>>> {
|
||||
let operand_1 = Self::paramater(context, 0).into_int_value();
|
||||
let operand_2 = Self::paramater(context, 1).into_int_value();
|
||||
const MAX_INDEX_BYTES: u64 = 31;
|
||||
|
||||
let is_overflow_bit = context.builder().build_int_compare(
|
||||
inkwell::IntPredicate::ULE,
|
||||
operand_1,
|
||||
context.word_const(MAX_INDEX_BYTES),
|
||||
"is_overflow_bit",
|
||||
)?;
|
||||
let is_overflow_byte = context.builder().build_int_z_extend(
|
||||
is_overflow_bit,
|
||||
context.byte_type(),
|
||||
"is_overflow_byte",
|
||||
)?;
|
||||
let mask_byte = context.builder().build_int_mul(
|
||||
context.byte_type().const_all_ones(),
|
||||
is_overflow_byte,
|
||||
"mask_byte",
|
||||
)?;
|
||||
let mask_byte_word = context.builder().build_int_z_extend(
|
||||
mask_byte,
|
||||
context.word_type(),
|
||||
"mask_byte_word",
|
||||
)?;
|
||||
|
||||
let index_truncated = context.builder().build_int_truncate(
|
||||
operand_1,
|
||||
context.byte_type(),
|
||||
"index_truncated",
|
||||
)?;
|
||||
let index_in_bits = context.builder().build_int_mul(
|
||||
index_truncated,
|
||||
context
|
||||
.byte_type()
|
||||
.const_int(revive_common::BIT_LENGTH_BYTE as u64, false),
|
||||
"index_in_bits",
|
||||
)?;
|
||||
let index_from_most_significant_bit = context.builder().build_int_sub(
|
||||
context.byte_type().const_int(
|
||||
MAX_INDEX_BYTES * revive_common::BIT_LENGTH_BYTE as u64,
|
||||
false,
|
||||
),
|
||||
index_in_bits,
|
||||
"index_from_msb",
|
||||
)?;
|
||||
let index_extended = context.builder().build_int_z_extend(
|
||||
index_from_most_significant_bit,
|
||||
context.word_type(),
|
||||
"index",
|
||||
)?;
|
||||
|
||||
let mask = context
|
||||
.builder()
|
||||
.build_left_shift(mask_byte_word, index_extended, "mask")?;
|
||||
let masked_value = context.builder().build_and(operand_2, mask, "masked")?;
|
||||
let byte =
|
||||
context
|
||||
.builder()
|
||||
.build_right_shift(masked_value, index_extended, false, "byte")?;
|
||||
|
||||
Ok(Some(byte.as_basic_value_enum()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<D> WriteLLVM<D> for Byte
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,257 @@
|
||||
//! Translates the arithmetic operations.
|
||||
|
||||
use inkwell::values::BasicValue;
|
||||
|
||||
use crate::polkavm::context::runtime::RuntimeFunction;
|
||||
use crate::polkavm::context::Context;
|
||||
use crate::polkavm::context::Pointer;
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
/// Implements the CALL operator according to the EVM specification.
|
||||
///
|
||||
/// # Arguments:
|
||||
/// - The address value.
|
||||
/// - The value value.
|
||||
/// - The input offset.
|
||||
/// - The input length.
|
||||
/// - The output offset.
|
||||
/// - The output length.
|
||||
/// - The deposit limit pointer.
|
||||
/// - The call flags.
|
||||
///
|
||||
/// # Returns:
|
||||
/// - The success value (as xlen)
|
||||
pub struct Call;
|
||||
|
||||
impl<D> RuntimeFunction<D> for Call
|
||||
where
|
||||
D: Dependency + Clone,
|
||||
{
|
||||
const NAME: &'static str = "__revive_call";
|
||||
|
||||
fn r#type<'ctx>(context: &Context<'ctx, D>) -> inkwell::types::FunctionType<'ctx> {
|
||||
context.register_type().fn_type(
|
||||
&[
|
||||
context.word_type().into(),
|
||||
context.word_type().into(),
|
||||
context.xlen_type().into(),
|
||||
context.xlen_type().into(),
|
||||
context.xlen_type().into(),
|
||||
context.xlen_type().into(),
|
||||
context.llvm().ptr_type(Default::default()).into(),
|
||||
context.xlen_type().into(),
|
||||
],
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
fn emit_body<'ctx>(
|
||||
&self,
|
||||
context: &mut Context<'ctx, D>,
|
||||
) -> anyhow::Result<Option<inkwell::values::BasicValueEnum<'ctx>>> {
|
||||
let address = Self::paramater(context, 0).into_int_value();
|
||||
let value = Self::paramater(context, 1).into_int_value();
|
||||
let input_offset = Self::paramater(context, 2).into_int_value();
|
||||
let input_length = Self::paramater(context, 3).into_int_value();
|
||||
let output_offset = Self::paramater(context, 4).into_int_value();
|
||||
let output_length = Self::paramater(context, 5).into_int_value();
|
||||
let depsit_limit_pointer = Self::paramater(context, 6).into_pointer_value();
|
||||
let flags = Self::paramater(context, 7).into_int_value();
|
||||
|
||||
let address_pointer = context.build_address_argument_store(address)?;
|
||||
|
||||
let value_pointer = context.build_alloca_at_entry(context.word_type(), "value_pointer");
|
||||
context.build_store(value_pointer, value)?;
|
||||
|
||||
let input_pointer = context.build_heap_gep(input_offset, input_length)?;
|
||||
let output_pointer = context.build_heap_gep(output_offset, output_length)?;
|
||||
|
||||
let output_length_pointer =
|
||||
context.build_alloca_at_entry(context.xlen_type(), "output_length");
|
||||
context.build_store(output_length_pointer, output_length)?;
|
||||
|
||||
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_limit_pointer = Pointer::new(
|
||||
context.word_type(),
|
||||
Default::default(),
|
||||
depsit_limit_pointer,
|
||||
);
|
||||
let deposit_and_value = revive_runtime_api::calling_convention::pack_hi_lo_reg(
|
||||
context.builder(),
|
||||
context.llvm(),
|
||||
deposit_limit_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(),
|
||||
input_length,
|
||||
input_pointer.to_int(context),
|
||||
"input_data",
|
||||
)?;
|
||||
let output_data = revive_runtime_api::calling_convention::pack_hi_lo_reg(
|
||||
context.builder(),
|
||||
context.llvm(),
|
||||
output_length_pointer.to_int(context),
|
||||
output_pointer.to_int(context),
|
||||
"output_data",
|
||||
)?;
|
||||
|
||||
let name = revive_runtime_api::polkavm_imports::CALL;
|
||||
let success = context
|
||||
.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(),
|
||||
input_data.into(),
|
||||
output_data.into(),
|
||||
],
|
||||
)
|
||||
.unwrap_or_else(|| panic!("{name} should return a value"))
|
||||
.into_int_value();
|
||||
Ok(Some(success.into()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<D> WriteLLVM<D> for Call
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
//! The front-end runtime functions.
|
||||
|
||||
pub mod arithmetics;
|
||||
pub mod bitwise;
|
||||
pub mod call;
|
||||
pub mod deploy_code;
|
||||
pub mod entry;
|
||||
pub mod revive;
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
//! 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::PolkaVMAdditionFunction;
|
||||
use crate::PolkaVMDivisionFunction;
|
||||
use crate::PolkaVMMultiplicationFunction;
|
||||
use crate::PolkaVMRemainderFunction;
|
||||
use crate::PolkaVMSignedDivisionFunction;
|
||||
use crate::PolkaVMSignedRemainderFunction;
|
||||
use crate::PolkaVMSubstractionFunction;
|
||||
|
||||
/// Translates the arithmetic addition.
|
||||
pub fn addition<'ctx, D>(
|
||||
@@ -19,10 +20,11 @@ pub fn addition<'ctx, D>(
|
||||
where
|
||||
D: Dependency + Clone,
|
||||
{
|
||||
let name = <PolkaVMAdditionFunction as RuntimeFunction<D>>::NAME;
|
||||
let declaration = <PolkaVMAdditionFunction as RuntimeFunction<D>>::declaration(context);
|
||||
Ok(context
|
||||
.builder()
|
||||
.build_int_add(operand_1, operand_2, "addition_result")?
|
||||
.as_basic_value_enum())
|
||||
.build_call(declaration, &[operand_1.into(), operand_2.into()], "SUB")
|
||||
.unwrap_or_else(|| panic!("revive runtime function {name} should return a value")))
|
||||
}
|
||||
|
||||
/// Translates the arithmetic subtraction.
|
||||
@@ -34,10 +36,11 @@ pub fn subtraction<'ctx, D>(
|
||||
where
|
||||
D: Dependency + Clone,
|
||||
{
|
||||
let name = <PolkaVMSubstractionFunction as RuntimeFunction<D>>::NAME;
|
||||
let declaration = <PolkaVMSubstractionFunction as RuntimeFunction<D>>::declaration(context);
|
||||
Ok(context
|
||||
.builder()
|
||||
.build_int_sub(operand_1, operand_2, "subtraction_result")?
|
||||
.as_basic_value_enum())
|
||||
.build_call(declaration, &[operand_1.into(), operand_2.into()], "SUB")
|
||||
.unwrap_or_else(|| panic!("revive runtime function {name} should return a value")))
|
||||
}
|
||||
|
||||
/// Translates the arithmetic multiplication.
|
||||
@@ -49,10 +52,11 @@ pub fn multiplication<'ctx, D>(
|
||||
where
|
||||
D: Dependency + Clone,
|
||||
{
|
||||
let name = <PolkaVMMultiplicationFunction as RuntimeFunction<D>>::NAME;
|
||||
let declaration = <PolkaVMMultiplicationFunction as RuntimeFunction<D>>::declaration(context);
|
||||
Ok(context
|
||||
.builder()
|
||||
.build_int_mul(operand_1, operand_2, "multiplication_result")?
|
||||
.as_basic_value_enum())
|
||||
.build_call(declaration, &[operand_1.into(), operand_2.into()], "MUL")
|
||||
.unwrap_or_else(|| panic!("revive runtime function {name} should return a value")))
|
||||
}
|
||||
|
||||
/// Translates the arithmetic division.
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
//! Translates the bitwise operations.
|
||||
|
||||
use inkwell::values::BasicValue;
|
||||
|
||||
use crate::polkavm::context::runtime::RuntimeFunction;
|
||||
use crate::polkavm::context::Context;
|
||||
use crate::polkavm::Dependency;
|
||||
use crate::{
|
||||
PolkaVMAndFunction, PolkaVMByteFunction, PolkaVMOrFunction, PolkaVMSarFunction,
|
||||
PolkaVMShlFunction, PolkaVMShrFunction, PolkaVMXorFunction,
|
||||
};
|
||||
|
||||
/// Translates the bitwise OR.
|
||||
pub fn or<'ctx, D>(
|
||||
@@ -14,10 +17,11 @@ pub fn or<'ctx, D>(
|
||||
where
|
||||
D: Dependency + Clone,
|
||||
{
|
||||
let name = <PolkaVMOrFunction as RuntimeFunction<D>>::NAME;
|
||||
let declaration = <PolkaVMOrFunction as RuntimeFunction<D>>::declaration(context);
|
||||
Ok(context
|
||||
.builder()
|
||||
.build_or(operand_1, operand_2, "or_result")?
|
||||
.as_basic_value_enum())
|
||||
.build_call(declaration, &[operand_1.into(), operand_2.into()], "OR")
|
||||
.unwrap_or_else(|| panic!("revive runtime function {name} should return a value")))
|
||||
}
|
||||
|
||||
/// Translates the bitwise XOR.
|
||||
@@ -29,10 +33,11 @@ pub fn xor<'ctx, D>(
|
||||
where
|
||||
D: Dependency + Clone,
|
||||
{
|
||||
let name = <PolkaVMXorFunction as RuntimeFunction<D>>::NAME;
|
||||
let declaration = <PolkaVMXorFunction as RuntimeFunction<D>>::declaration(context);
|
||||
Ok(context
|
||||
.builder()
|
||||
.build_xor(operand_1, operand_2, "xor_result")?
|
||||
.as_basic_value_enum())
|
||||
.build_call(declaration, &[operand_1.into(), operand_2.into()], "XOR")
|
||||
.unwrap_or_else(|| panic!("revive runtime function {name} should return a value")))
|
||||
}
|
||||
|
||||
/// Translates the bitwise AND.
|
||||
@@ -44,10 +49,11 @@ pub fn and<'ctx, D>(
|
||||
where
|
||||
D: Dependency + Clone,
|
||||
{
|
||||
let name = <PolkaVMAndFunction as RuntimeFunction<D>>::NAME;
|
||||
let declaration = <PolkaVMAndFunction as RuntimeFunction<D>>::declaration(context);
|
||||
Ok(context
|
||||
.builder()
|
||||
.build_and(operand_1, operand_2, "and_result")?
|
||||
.as_basic_value_enum())
|
||||
.build_call(declaration, &[operand_1.into(), operand_2.into()], "AND")
|
||||
.unwrap_or_else(|| panic!("revive runtime function {name} should return a value")))
|
||||
}
|
||||
|
||||
/// Translates the bitwise shift left.
|
||||
@@ -59,37 +65,11 @@ pub fn shift_left<'ctx, D>(
|
||||
where
|
||||
D: Dependency + Clone,
|
||||
{
|
||||
let overflow_block = context.append_basic_block("shift_left_overflow");
|
||||
let non_overflow_block = context.append_basic_block("shift_left_non_overflow");
|
||||
let join_block = context.append_basic_block("shift_left_join");
|
||||
|
||||
let condition_is_overflow = context.builder().build_int_compare(
|
||||
inkwell::IntPredicate::UGT,
|
||||
shift,
|
||||
context.word_const((revive_common::BIT_LENGTH_WORD - 1) as u64),
|
||||
"shift_left_is_overflow",
|
||||
)?;
|
||||
context.build_conditional_branch(condition_is_overflow, overflow_block, non_overflow_block)?;
|
||||
|
||||
context.set_basic_block(overflow_block);
|
||||
context.build_unconditional_branch(join_block);
|
||||
|
||||
context.set_basic_block(non_overflow_block);
|
||||
let value =
|
||||
context
|
||||
.builder()
|
||||
.build_left_shift(value, shift, "shift_left_non_overflow_result")?;
|
||||
context.build_unconditional_branch(join_block);
|
||||
|
||||
context.set_basic_block(join_block);
|
||||
let result = context
|
||||
.builder()
|
||||
.build_phi(context.word_type(), "shift_left_value")?;
|
||||
result.add_incoming(&[
|
||||
(&value, non_overflow_block),
|
||||
(&context.word_const(0), overflow_block),
|
||||
]);
|
||||
Ok(result.as_basic_value())
|
||||
let name = <PolkaVMShlFunction as RuntimeFunction<D>>::NAME;
|
||||
let declaration = <PolkaVMShlFunction as RuntimeFunction<D>>::declaration(context);
|
||||
Ok(context
|
||||
.build_call(declaration, &[shift.into(), value.into()], "SHL")
|
||||
.unwrap_or_else(|| panic!("revive runtime function {name} should return a value")))
|
||||
}
|
||||
|
||||
/// Translates the bitwise shift right.
|
||||
@@ -101,39 +81,11 @@ pub fn shift_right<'ctx, D>(
|
||||
where
|
||||
D: Dependency + Clone,
|
||||
{
|
||||
let overflow_block = context.append_basic_block("shift_right_overflow");
|
||||
let non_overflow_block = context.append_basic_block("shift_right_non_overflow");
|
||||
let join_block = context.append_basic_block("shift_right_join");
|
||||
|
||||
let condition_is_overflow = context.builder().build_int_compare(
|
||||
inkwell::IntPredicate::UGT,
|
||||
shift,
|
||||
context.word_const((revive_common::BIT_LENGTH_WORD - 1) as u64),
|
||||
"shift_right_is_overflow",
|
||||
)?;
|
||||
context.build_conditional_branch(condition_is_overflow, overflow_block, non_overflow_block)?;
|
||||
|
||||
context.set_basic_block(overflow_block);
|
||||
context.build_unconditional_branch(join_block);
|
||||
|
||||
context.set_basic_block(non_overflow_block);
|
||||
let value = context.builder().build_right_shift(
|
||||
value,
|
||||
shift,
|
||||
false,
|
||||
"shift_right_non_overflow_result",
|
||||
)?;
|
||||
context.build_unconditional_branch(join_block);
|
||||
|
||||
context.set_basic_block(join_block);
|
||||
let result = context
|
||||
.builder()
|
||||
.build_phi(context.word_type(), "shift_right_value")?;
|
||||
result.add_incoming(&[
|
||||
(&value, non_overflow_block),
|
||||
(&context.word_const(0), overflow_block),
|
||||
]);
|
||||
Ok(result.as_basic_value())
|
||||
let name = <PolkaVMShrFunction as RuntimeFunction<D>>::NAME;
|
||||
let declaration = <PolkaVMShrFunction as RuntimeFunction<D>>::declaration(context);
|
||||
Ok(context
|
||||
.build_call(declaration, &[shift.into(), value.into()], "SHR")
|
||||
.unwrap_or_else(|| panic!("revive runtime function {name} should return a value")))
|
||||
}
|
||||
|
||||
/// Translates the arithmetic bitwise shift right.
|
||||
@@ -145,68 +97,11 @@ pub fn shift_right_arithmetic<'ctx, D>(
|
||||
where
|
||||
D: Dependency + Clone,
|
||||
{
|
||||
let overflow_block = context.append_basic_block("shift_right_arithmetic_overflow");
|
||||
let overflow_positive_block =
|
||||
context.append_basic_block("shift_right_arithmetic_overflow_positive");
|
||||
let overflow_negative_block =
|
||||
context.append_basic_block("shift_right_arithmetic_overflow_negative");
|
||||
let non_overflow_block = context.append_basic_block("shift_right_arithmetic_non_overflow");
|
||||
let join_block = context.append_basic_block("shift_right_arithmetic_join");
|
||||
|
||||
let condition_is_overflow = context.builder().build_int_compare(
|
||||
inkwell::IntPredicate::UGT,
|
||||
shift,
|
||||
context.word_const((revive_common::BIT_LENGTH_WORD - 1) as u64),
|
||||
"shift_right_arithmetic_is_overflow",
|
||||
)?;
|
||||
context.build_conditional_branch(condition_is_overflow, overflow_block, non_overflow_block)?;
|
||||
|
||||
context.set_basic_block(overflow_block);
|
||||
let sign_bit = context.builder().build_right_shift(
|
||||
value,
|
||||
context.word_const((revive_common::BIT_LENGTH_WORD - 1) as u64),
|
||||
false,
|
||||
"shift_right_arithmetic_sign_bit",
|
||||
)?;
|
||||
let condition_is_negative = context.builder().build_int_truncate_or_bit_cast(
|
||||
sign_bit,
|
||||
context.bool_type(),
|
||||
"shift_right_arithmetic_sign_bit_truncated",
|
||||
)?;
|
||||
context.build_conditional_branch(
|
||||
condition_is_negative,
|
||||
overflow_negative_block,
|
||||
overflow_positive_block,
|
||||
)?;
|
||||
|
||||
context.set_basic_block(overflow_positive_block);
|
||||
context.build_unconditional_branch(join_block);
|
||||
|
||||
context.set_basic_block(overflow_negative_block);
|
||||
context.build_unconditional_branch(join_block);
|
||||
|
||||
context.set_basic_block(non_overflow_block);
|
||||
let value = context.builder().build_right_shift(
|
||||
value,
|
||||
shift,
|
||||
true,
|
||||
"shift_right_arithmetic_non_overflow_result",
|
||||
)?;
|
||||
context.build_unconditional_branch(join_block);
|
||||
|
||||
context.set_basic_block(join_block);
|
||||
let result = context
|
||||
.builder()
|
||||
.build_phi(context.word_type(), "shift_arithmetic_right_value")?;
|
||||
result.add_incoming(&[
|
||||
(&value, non_overflow_block),
|
||||
(
|
||||
&context.word_type().const_all_ones(),
|
||||
overflow_negative_block,
|
||||
),
|
||||
(&context.word_const(0), overflow_positive_block),
|
||||
]);
|
||||
Ok(result.as_basic_value())
|
||||
let name = <PolkaVMSarFunction as RuntimeFunction<D>>::NAME;
|
||||
let declaration = <PolkaVMSarFunction as RuntimeFunction<D>>::declaration(context);
|
||||
Ok(context
|
||||
.build_call(declaration, &[shift.into(), value.into()], "SHR")
|
||||
.unwrap_or_else(|| panic!("revive runtime function {name} should return a value")))
|
||||
}
|
||||
|
||||
/// Translates the `byte` instruction, extracting the byte of `operand_2`
|
||||
@@ -225,61 +120,9 @@ pub fn byte<'ctx, D>(
|
||||
where
|
||||
D: Dependency + Clone,
|
||||
{
|
||||
const MAX_INDEX_BYTES: u64 = 31;
|
||||
|
||||
let is_overflow_bit = context.builder().build_int_compare(
|
||||
inkwell::IntPredicate::ULE,
|
||||
operand_1,
|
||||
context.word_const(MAX_INDEX_BYTES),
|
||||
"is_overflow_bit",
|
||||
)?;
|
||||
let is_overflow_byte = context.builder().build_int_z_extend(
|
||||
is_overflow_bit,
|
||||
context.byte_type(),
|
||||
"is_overflow_byte",
|
||||
)?;
|
||||
let mask_byte = context.builder().build_int_mul(
|
||||
context.byte_type().const_all_ones(),
|
||||
is_overflow_byte,
|
||||
"mask_byte",
|
||||
)?;
|
||||
let mask_byte_word =
|
||||
context
|
||||
.builder()
|
||||
.build_int_z_extend(mask_byte, context.word_type(), "mask_byte_word")?;
|
||||
|
||||
let index_truncated =
|
||||
context
|
||||
.builder()
|
||||
.build_int_truncate(operand_1, context.byte_type(), "index_truncated")?;
|
||||
let index_in_bits = context.builder().build_int_mul(
|
||||
index_truncated,
|
||||
context
|
||||
.byte_type()
|
||||
.const_int(revive_common::BIT_LENGTH_BYTE as u64, false),
|
||||
"index_in_bits",
|
||||
)?;
|
||||
let index_from_most_significant_bit = context.builder().build_int_sub(
|
||||
context.byte_type().const_int(
|
||||
MAX_INDEX_BYTES * revive_common::BIT_LENGTH_BYTE as u64,
|
||||
false,
|
||||
),
|
||||
index_in_bits,
|
||||
"index_from_msb",
|
||||
)?;
|
||||
let index_extended = context.builder().build_int_z_extend(
|
||||
index_from_most_significant_bit,
|
||||
context.word_type(),
|
||||
"index",
|
||||
)?;
|
||||
|
||||
let mask = context
|
||||
.builder()
|
||||
.build_left_shift(mask_byte_word, index_extended, "mask")?;
|
||||
let masked_value = context.builder().build_and(operand_2, mask, "masked")?;
|
||||
let byte = context
|
||||
.builder()
|
||||
.build_right_shift(masked_value, index_extended, false, "byte")?;
|
||||
|
||||
Ok(byte.as_basic_value_enum())
|
||||
let name = <PolkaVMByteFunction as RuntimeFunction<D>>::NAME;
|
||||
let declaration = <PolkaVMByteFunction as RuntimeFunction<D>>::declaration(context);
|
||||
Ok(context
|
||||
.build_call(declaration, &[operand_1.into(), operand_2.into()], "BYTE")
|
||||
.unwrap_or_else(|| panic!("revive runtime function {name} should return a value")))
|
||||
}
|
||||
|
||||
@@ -3,12 +3,13 @@
|
||||
use inkwell::values::BasicValue;
|
||||
|
||||
use crate::polkavm::context::argument::Argument;
|
||||
use crate::polkavm::context::runtime::RuntimeFunction;
|
||||
use crate::polkavm::context::Context;
|
||||
use crate::polkavm::Dependency;
|
||||
use crate::{PolkaVMCall, PolkaVMCallReentrancyProtector};
|
||||
|
||||
const STATIC_CALL_FLAG: u32 = 0b0001_0000;
|
||||
const REENTRANT_CALL_FLAG: u32 = 0b0000_1000;
|
||||
const SOLIDITY_TRANSFER_GAS_STIPEND_THRESHOLD: u64 = 2300;
|
||||
|
||||
/// Translates a contract call.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
@@ -27,79 +28,45 @@ pub fn call<'ctx, D>(
|
||||
where
|
||||
D: Dependency + Clone,
|
||||
{
|
||||
let address_pointer = context.build_address_argument_store(address)?;
|
||||
|
||||
let value = value.unwrap_or_else(|| context.word_const(0));
|
||||
let value_pointer = context.build_alloca_at_entry(context.word_type(), "value_pointer");
|
||||
context.build_store(value_pointer, value)?;
|
||||
|
||||
let input_offset = context.safe_truncate_int_to_xlen(input_offset)?;
|
||||
let input_length = context.safe_truncate_int_to_xlen(input_length)?;
|
||||
let output_offset = context.safe_truncate_int_to_xlen(output_offset)?;
|
||||
|
||||
let input_length = context.safe_truncate_int_to_xlen(input_length)?;
|
||||
let output_length = context.safe_truncate_int_to_xlen(output_length)?;
|
||||
|
||||
let input_pointer = context.build_heap_gep(input_offset, input_length)?;
|
||||
let output_pointer = context.build_heap_gep(output_offset, output_length)?;
|
||||
|
||||
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 deposit_limit_pointer =
|
||||
context.build_alloca_at_entry(context.word_type(), "deposit_pointer");
|
||||
let flags = 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(),
|
||||
)
|
||||
context.build_store(deposit_limit_pointer, context.word_type().const_zero())?;
|
||||
context.xlen_type().const_int(flags as u64, false)
|
||||
} else {
|
||||
call_reentrancy_heuristic(context, gas, input_length, output_length)?
|
||||
call_reentrancy_heuristic(
|
||||
context,
|
||||
deposit_limit_pointer.value,
|
||||
gas,
|
||||
input_length,
|
||||
output_length,
|
||||
)?
|
||||
};
|
||||
|
||||
let deposit_pointer = context.build_alloca_at_entry(context.word_type(), "deposit_pointer");
|
||||
context.build_store(deposit_pointer, deposit_limit_value)?;
|
||||
let value = value.unwrap_or_else(|| context.word_const(0));
|
||||
|
||||
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(),
|
||||
input_length,
|
||||
input_pointer.to_int(context),
|
||||
"input_data",
|
||||
)?;
|
||||
let output_data = revive_runtime_api::calling_convention::pack_hi_lo_reg(
|
||||
context.builder(),
|
||||
context.llvm(),
|
||||
output_length_pointer.to_int(context),
|
||||
output_pointer.to_int(context),
|
||||
"output_data",
|
||||
)?;
|
||||
|
||||
let name = revive_runtime_api::polkavm_imports::CALL;
|
||||
let name = <PolkaVMCall as RuntimeFunction<D>>::NAME;
|
||||
let declaration = <PolkaVMCall as RuntimeFunction<D>>::declaration(context);
|
||||
let arguments = &[
|
||||
address.into(),
|
||||
value.into(),
|
||||
input_offset.into(),
|
||||
input_length.into(),
|
||||
output_offset.into(),
|
||||
output_length.into(),
|
||||
deposit_limit_pointer.value.into(),
|
||||
flags.into(),
|
||||
];
|
||||
let success = context
|
||||
.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(),
|
||||
input_data.into(),
|
||||
output_data.into(),
|
||||
],
|
||||
)
|
||||
.unwrap_or_else(|| panic!("{name} should return a value"))
|
||||
.build_call(declaration, arguments, "call")
|
||||
.unwrap_or_else(|| panic!("revive runtime function {name} should return a value"))
|
||||
.into_int_value();
|
||||
|
||||
let is_success = context.builder().build_int_compare(
|
||||
@@ -216,85 +183,28 @@ where
|
||||
.as_basic_value_enum())
|
||||
}
|
||||
|
||||
/// 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, D>(
|
||||
context: &mut Context<'ctx, D>,
|
||||
deposit_limit_pointer: inkwell::values::PointerValue<'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>,
|
||||
)>
|
||||
) -> anyhow::Result<inkwell::values::IntValue<'ctx>>
|
||||
where
|
||||
D: Dependency + Clone,
|
||||
{
|
||||
// Branch-free SSA implementation: First derive the heuristic boolean (int1) value.
|
||||
let input_length_or_output_length =
|
||||
let name = <PolkaVMCallReentrancyProtector as RuntimeFunction<D>>::NAME;
|
||||
let declaration = <PolkaVMCallReentrancyProtector as RuntimeFunction<D>>::declaration(context);
|
||||
let arguments = &[
|
||||
deposit_limit_pointer.into(),
|
||||
gas.into(),
|
||||
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_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",
|
||||
)?;
|
||||
|
||||
Ok((call_flags, deposit_limit_value))
|
||||
.build_or(input_length, output_length, "input_length_or_output_length")?
|
||||
.into(),
|
||||
];
|
||||
Ok(context
|
||||
.build_call(declaration, arguments, "call_flags")
|
||||
.unwrap_or_else(|| panic!("revive runtime function {name} should return a value"))
|
||||
.into_int_value())
|
||||
}
|
||||
|
||||
@@ -204,11 +204,25 @@ where
|
||||
revive_llvm_context::PolkaVMEventLogFunction::<3>.declare(context)?;
|
||||
revive_llvm_context::PolkaVMEventLogFunction::<4>.declare(context)?;
|
||||
|
||||
revive_llvm_context::PolkaVMAdditionFunction.declare(context)?;
|
||||
revive_llvm_context::PolkaVMSubstractionFunction.declare(context)?;
|
||||
revive_llvm_context::PolkaVMMultiplicationFunction.declare(context)?;
|
||||
revive_llvm_context::PolkaVMDivisionFunction.declare(context)?;
|
||||
revive_llvm_context::PolkaVMSignedDivisionFunction.declare(context)?;
|
||||
revive_llvm_context::PolkaVMRemainderFunction.declare(context)?;
|
||||
revive_llvm_context::PolkaVMSignedRemainderFunction.declare(context)?;
|
||||
|
||||
revive_llvm_context::PolkaVMOrFunction.declare(context)?;
|
||||
revive_llvm_context::PolkaVMXorFunction.declare(context)?;
|
||||
revive_llvm_context::PolkaVMAndFunction.declare(context)?;
|
||||
revive_llvm_context::PolkaVMShlFunction.declare(context)?;
|
||||
revive_llvm_context::PolkaVMShrFunction.declare(context)?;
|
||||
revive_llvm_context::PolkaVMSarFunction.declare(context)?;
|
||||
revive_llvm_context::PolkaVMByteFunction.declare(context)?;
|
||||
|
||||
revive_llvm_context::PolkaVMCall.declare(context)?;
|
||||
revive_llvm_context::PolkaVMCallReentrancyProtector.declare(context)?;
|
||||
|
||||
revive_llvm_context::PolkaVMSbrkFunction.declare(context)?;
|
||||
|
||||
let mut entry = revive_llvm_context::PolkaVMEntryFunction::default();
|
||||
@@ -258,11 +272,25 @@ where
|
||||
revive_llvm_context::PolkaVMEventLogFunction::<3>.into_llvm(context)?;
|
||||
revive_llvm_context::PolkaVMEventLogFunction::<4>.into_llvm(context)?;
|
||||
|
||||
revive_llvm_context::PolkaVMAdditionFunction.into_llvm(context)?;
|
||||
revive_llvm_context::PolkaVMSubstractionFunction.into_llvm(context)?;
|
||||
revive_llvm_context::PolkaVMMultiplicationFunction.into_llvm(context)?;
|
||||
revive_llvm_context::PolkaVMDivisionFunction.into_llvm(context)?;
|
||||
revive_llvm_context::PolkaVMSignedDivisionFunction.into_llvm(context)?;
|
||||
revive_llvm_context::PolkaVMRemainderFunction.into_llvm(context)?;
|
||||
revive_llvm_context::PolkaVMSignedRemainderFunction.into_llvm(context)?;
|
||||
|
||||
revive_llvm_context::PolkaVMOrFunction.into_llvm(context)?;
|
||||
revive_llvm_context::PolkaVMXorFunction.into_llvm(context)?;
|
||||
revive_llvm_context::PolkaVMAndFunction.into_llvm(context)?;
|
||||
revive_llvm_context::PolkaVMShlFunction.into_llvm(context)?;
|
||||
revive_llvm_context::PolkaVMShrFunction.into_llvm(context)?;
|
||||
revive_llvm_context::PolkaVMSarFunction.into_llvm(context)?;
|
||||
revive_llvm_context::PolkaVMByteFunction.into_llvm(context)?;
|
||||
|
||||
revive_llvm_context::PolkaVMCall.into_llvm(context)?;
|
||||
revive_llvm_context::PolkaVMCallReentrancyProtector.into_llvm(context)?;
|
||||
|
||||
revive_llvm_context::PolkaVMSbrkFunction.into_llvm(context)?;
|
||||
|
||||
Ok(())
|
||||
|
||||
Reference in New Issue
Block a user