Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

pallet-xvm refactor #980

Merged
merged 27 commits into from
Jul 28, 2023
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
49de894
pallet-xvm refactor.
shaunxw Jul 20, 2023
5ea897c
Add TODOs.
shaunxw Jul 20, 2023
57ad0c9
Update design.
shaunxw Jul 20, 2023
46263d2
Keep XVM call interface unified.
shaunxw Jul 20, 2023
5c79244
Renaming.
shaunxw Jul 20, 2023
2a81a2d
update & integration.
shaunxw Jul 25, 2023
e576f74
Update xvm precompiles mock & tests.
shaunxw Jul 25, 2023
401cf8c
Replace 'UnknownError' with concrete errors.
shaunxw Jul 26, 2023
e410631
Update CE & precompile.
shaunxw Jul 26, 2023
819c7f8
Clean up.
shaunxw Jul 26, 2023
50c04b2
Benchmarks and mock.
shaunxw Jul 26, 2023
eed8ce4
Merge branch 'master' into feat/pallet-xvm-refactor
shaunxw Jul 26, 2023
6b22ce6
Updates for polkadot-v0.9.43.
shaunxw Jul 26, 2023
61220e6
Fix benchmarks.
shaunxw Jul 26, 2023
7b92705
Add benchmarking result and weight info.
shaunxw Jul 26, 2023
8831ba9
Add license header to weight.rs.
shaunxw Jul 26, 2023
5666403
Add pallet description docstring.
shaunxw Jul 27, 2023
df8e062
Record gas cost in XVM precompile.
shaunxw Jul 27, 2023
34a3942
Less weight is available with overheads cost.
shaunxw Jul 27, 2023
e9ccd4b
Trace Ethereum transact result.
shaunxw Jul 27, 2023
cd5d2af
Handle record cost result.
shaunxw Jul 27, 2023
8d4f62e
Bump Shibuya semver and spec versoin.
shaunxw Jul 27, 2023
7482e7b
Apply review suggestions.
shaunxw Jul 27, 2023
3fa315e
Update with new benchmarking result.
shaunxw Jul 27, 2023
931117c
Improve XVM call benchmarking.
shaunxw Jul 27, 2023
ff3b41f
Make local/shibuya/shiden/astar runtimes and client have the same sem…
shaunxw Jul 27, 2023
f6a81fa
Update with new benchmarking result.
shaunxw Jul 27, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

59 changes: 59 additions & 0 deletions pallets/xvm/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
[package]
name = "pallet-xvm-v2"
version = "0.2.1"
authors.workspace = true
edition.workspace = true
homepage.workspace = true
repository.workspace = true

[dependencies]
impl-trait-for-tuples = { workspace = true }
log = { workspace = true }
serde = { workspace = true, optional = true }

# Substrate
frame-support = { workspace = true }
frame-system = { workspace = true }
parity-scale-codec = { workspace = true }
scale-info = { workspace = true }
sp-core = { workspace = true }
sp-runtime = { workspace = true }
sp-std = { workspace = true }

# Benchmarks
frame-benchmarking = { workspace = true, optional = true }

# EVM
pallet-evm = { workspace = true }

# Substrate WASM VM support
pallet-contracts = { workspace = true }

# Astar
astar-primitives = { workspace = true }
pallet-ethereum-checked = { workspace = true }

[dev-dependencies]

[features]
default = ["std"]
std = [
"parity-scale-codec/std",
"frame-support/std",
"frame-system/std",
"pallet-contracts/std",
"pallet-evm/std",
"scale-info/std",
"serde",
"sp-core/std",
"sp-runtime/std",
"sp-std/std",
"astar-primitives/std",
"pallet-ethereum-checked/std",
]

runtime-benchmarks = [
"frame-benchmarking",
"pallet-ethereum-checked/runtime-benchmarks",
]
try-runtime = ["frame-support/try-runtime"]
302 changes: 302 additions & 0 deletions pallets/xvm/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,302 @@
// This file is part of Astar.

// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later

// Astar is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// Astar is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with Astar. If not, see <http://www.gnu.org/licenses/>.

//! # XVM pallet
//!
//! ## Overview
//!
//! ## Interface
//!
//! ### Dispatchable Function
//!
//!
//! ### Other
//!
//!

use frame_support::{pallet_prelude::*, traits::ConstU32, BoundedVec};
use pallet_evm::GasWeightMapping;
use parity_scale_codec::{Decode, Encode};
use scale_info::TypeInfo;
use sp_core::U256;
use sp_runtime::{traits::StaticLookup, RuntimeDebug};
use sp_std::{marker::PhantomData, prelude::*, result::Result};

use astar_primitives::ethereum_checked::{
AccountMapping, CheckedEthereumTransact, CheckedEthereumTx, MAX_ETHEREUM_TX_INPUT_SIZE,
};

pub use pallet::*;

// TODO: move types and traits to `astar-primitives`.

/// XVM call info on success.
#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo)]
pub struct CallInfo {
/// Output of the call.
pub output: Vec<u8>,
/// Actual used weight.
pub used_weight: Weight,
}
ashutoshvarma marked this conversation as resolved.
Show resolved Hide resolved

/// XVM call error on failure.
#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo)]
pub enum CallError {
/// The call failed on EVM or WASM execution.
ExecutionFailed(Vec<u8>),
/// Input is too large.
InputTooLarge,
/// Target contract address is invalid.
InvalidTarget,
/// Calling the contracts in the same VM is not allowed.
SameVmCallNotAllowed,
}

/// XVM call error with used weight info.
#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo)]
pub struct CallErrorWithWeight {
/// Error info.
pub error: CallError,
/// Actual used weight.
pub used_weight: Weight,
}

/// XVM call result.
pub type XvmCallResult = Result<CallInfo, CallErrorWithWeight>;

#[derive(PartialEq, Eq, Copy, Clone, Encode, Decode, RuntimeDebug, TypeInfo)]
pub enum Vm {
Evm,
Wasm,
}

// TODO: Note caller shouldn't be able to specify `source_vm`.
/// XVM context.
#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo)]
pub struct XvmContext {
/// The source VM of the call.
pub source_vm: Vm,
/// The target VM of the call.
pub target_vm: Vm,
/// Max weight limit.
pub weight_limit: Weight,
/// Optional encoded execution environment.
pub env: Option<Vec<u8>>,
}

pub trait XvmCall<AccountId> {
/// Call a contract in XVM.
///
/// Parameters:
/// - `context`: XVM context.
/// - `source`: Caller Id.
/// - `target`: Target contract address.
/// - `input`: call input data.
fn xvm_call(
context: XvmContext,
source: AccountId,
target: Vec<u8>,
shaunxw marked this conversation as resolved.
Show resolved Hide resolved
input: Vec<u8>,
ashutoshvarma marked this conversation as resolved.
Show resolved Hide resolved
) -> XvmCallResult;
}

#[frame_support::pallet]
pub mod pallet {
use super::*;

#[pallet::pallet]
pub struct Pallet<T>(PhantomData<T>);

#[pallet::config]
pub trait Config:
frame_system::Config
+ pallet_evm::Config
+ pallet_ethereum_checked::Config
+ pallet_contracts::Config
{
/// `CheckedEthereumTransact` implementation.
type EthereumTransact: CheckedEthereumTransact;
}
}

impl<T: Config> XvmCall<T::AccountId> for Pallet<T> {
fn xvm_call(
context: XvmContext,
source: T::AccountId,
target: Vec<u8>,
input: Vec<u8>,
) -> XvmCallResult {
Pallet::<T>::do_xvm_call(context, source, target, input, false)
}
}

// TODO: benchmark XVM calls overhead
pub const PLACEHOLDER_WEIGHT: Weight = Weight::from_parts(1_000_000, 1024);

impl<T: Config> Pallet<T> {
fn do_xvm_call(
context: XvmContext,
source: T::AccountId,
target: Vec<u8>,
input: Vec<u8>,
skip_execution: bool,
) -> XvmCallResult {
if context.source_vm == context.target_vm {
return Err(CallErrorWithWeight {
error: CallError::SameVmCallNotAllowed,
used_weight: PLACEHOLDER_WEIGHT,
});
}

Dinonard marked this conversation as resolved.
Show resolved Hide resolved
match context.source_vm {
Vm::Evm => Pallet::<T>::evm_call(context, source, target, input, skip_execution),
Vm::Wasm => Pallet::<T>::wasm_call(context, source, target, input, skip_execution),
}
}

fn evm_call(
context: XvmContext,
source: T::AccountId,
target: Vec<u8>,
input: Vec<u8>,
skip_execution: bool,
) -> XvmCallResult {
log::trace!(
target: "xvm::evm_call",
"Calling EVM: {:?} {:?}, {:?}, {:?}",
context, source, target, input,
);

let value = U256::zero();
let gas_limit = T::GasWeightMapping::weight_to_gas(context.weight_limit);

let target_decoded =
Decode::decode(&mut target.as_ref()).map_err(|_| CallErrorWithWeight {
error: CallError::InvalidTarget,
used_weight: PLACEHOLDER_WEIGHT,
})?;
let bounded_input = BoundedVec::<u8, ConstU32<MAX_ETHEREUM_TX_INPUT_SIZE>>::try_from(input)
.map_err(|_| CallErrorWithWeight {
error: CallError::InputTooLarge,
used_weight: PLACEHOLDER_WEIGHT,
})?;

if skip_execution {
shaunxw marked this conversation as resolved.
Show resolved Hide resolved
return Ok(CallInfo {
output: vec![],
used_weight: PLACEHOLDER_WEIGHT,
});
}

let (post_dispatch_info, call_info) = T::EthereumTransact::xvm_transact(
T::AccountMapping::into_h160(source),
CheckedEthereumTx {
gas_limit: U256::from(gas_limit),
target: target_decoded,
value,
input: bounded_input,
maybe_access_list: None,
},
)
.map_err(|e| {
let used_weight = e.post_info.actual_weight.unwrap_or_default();
CallErrorWithWeight {
error: CallError::ExecutionFailed(Into::<&str>::into(e.error).into()),
used_weight,
}
})?;

log::trace!(
target: "xvm::evm_call",
"EVM call result: exit_reason: {:?}, used_gas: {:?}", call_info.exit_reason, call_info.used_gas,
);

// TODO: add overhead to used weight
Ok(CallInfo {
output: call_info.value,
used_weight: post_dispatch_info.actual_weight.unwrap_or_default(),
})
}

fn wasm_call(
context: XvmContext,
source: T::AccountId,
target: Vec<u8>,
input: Vec<u8>,
skip_execution: bool,
) -> XvmCallResult {
log::trace!(
target: "xvm::wasm_call",
"Calling WASM: {:?} {:?}, {:?}, {:?}",
context, source, target, input,
);

let dest = {
let error = CallErrorWithWeight {
error: CallError::InvalidTarget,
used_weight: PLACEHOLDER_WEIGHT,
};
let decoded = Decode::decode(&mut target.as_ref()).map_err(|_| error.clone())?;
T::Lookup::lookup(decoded).map_err(|_| error)
}?;

if skip_execution {
return Ok(CallInfo {
output: vec![],
used_weight: PLACEHOLDER_WEIGHT,
});
}

let call_result = pallet_contracts::Pallet::<T>::bare_call(
source,
dest,
Default::default(),
context.weight_limit,
None,
input,
false,
pallet_contracts::Determinism::Deterministic,
);
log::trace!(target: "xvm::wasm_call", "WASM call result: {:?}", call_result);

// TODO: add overhead to used weight
let used_weight = call_result.gas_consumed;

match call_result.result {
Ok(success) => Ok(CallInfo {
output: success.data,
used_weight,
}),

Err(error) => Err(CallErrorWithWeight {
error: CallError::ExecutionFailed(Into::<&str>::into(error).into()),
used_weight,
}),
}
}

#[cfg(feature = "runtime-benchmarks")]
pub fn xvm_call_without_execution(
context: XvmContext,
source: T::AccountId,
target: Vec<u8>,
input: Vec<u8>,
) -> XvmCallResult {
Self::do_xvm_call(context, source, target, input, true)
}
}
Loading