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

Cross-VM payable calls & XVM tests #988

Merged
merged 17 commits into from
Aug 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 8 additions & 0 deletions Cargo.lock

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

7 changes: 4 additions & 3 deletions chain-extensions/types/xvm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

#![cfg_attr(not(feature = "std"), no_std)]

use astar_primitives::xvm::CallError;
use astar_primitives::{xvm::CallError, Balance};
use parity_scale_codec::{Decode, Encode};
use sp_std::vec::Vec;

Expand All @@ -41,8 +41,7 @@ impl From<CallError> for XvmExecutionResult {
SameVmCallNotAllowed => 2,
InvalidTarget => 3,
InputTooLarge => 4,
BadOrigin => 5,
ExecutionFailed(_) => 6,
ExecutionFailed(_) => 5,
};
Self::Err(error_code)
}
Expand All @@ -65,4 +64,6 @@ pub struct XvmCallArgs {
pub to: Vec<u8>,
/// Encoded call params
pub input: Vec<u8>,
/// Value to transfer
pub value: Balance,
}
36 changes: 13 additions & 23 deletions chain-extensions/xvm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,15 @@

#![cfg_attr(not(feature = "std"), no_std)]

use astar_primitives::xvm::{CallError, Context, VmId, XvmCall};
use astar_primitives::xvm::{Context, VmId, XvmCall};
use frame_support::dispatch::Encode;
use pallet_contracts::{
chain_extension::{ChainExtension, Environment, Ext, InitState, RetVal},
Origin,
};
use pallet_contracts::chain_extension::{ChainExtension, Environment, Ext, InitState, RetVal};
use sp_runtime::DispatchError;
use sp_std::marker::PhantomData;
use xvm_chain_extension_types::{XvmCallArgs, XvmExecutionResult};

enum XvmFuncId {
Call,
// TODO: expand with other calls too
}

impl TryFrom<u16> for XvmFuncId {
Expand Down Expand Up @@ -76,28 +72,22 @@ where
// So we will charge a 32KB dummy value as a temporary replacement.
let charged_weight = env.charge_weight(weight_limit.set_proof_size(32 * 1024))?;

let caller = match env.ext().caller().clone() {
Origin::Signed(address) => address,
Origin::Root => {
log::trace!(
target: "xvm-extension::xvm_call",
"root origin not supported"
);
return Ok(RetVal::Converging(
XvmExecutionResult::from(CallError::BadOrigin).into(),
));
}
};
let XvmCallArgs {
vm_id,
to,
input,
value,
} = env.read_as_unbounded(env.in_len())?;

let XvmCallArgs { vm_id, to, input } = env.read_as_unbounded(env.in_len())?;
// Similar to EVM behavior, the `source` should be (limited to) the
// contract address. Otherwise contracts would be able to do arbitrary
// things on behalf of the caller via XVM.
let source = env.ext().address();

let _origin_address = env.ext().address().clone();
let _value = env.ext().value_transferred();
let xvm_context = Context {
source_vm_id: VmId::Wasm,
weight_limit,
};

let vm_id = {
match TryInto::<VmId>::try_into(vm_id) {
Ok(id) => id,
Expand All @@ -108,7 +98,7 @@ where
}
}
};
let call_result = XC::call(xvm_context, vm_id, caller, to, input);
let call_result = XC::call(xvm_context, vm_id, source.clone(), to, input, value);

let actual_weight = match call_result {
Ok(ref info) => info.used_weight,
Expand Down
4 changes: 2 additions & 2 deletions pallets/ethereum-checked/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

use super::*;

use astar_primitives::ethereum_checked::MAX_ETHEREUM_TX_INPUT_SIZE;
use astar_primitives::ethereum_checked::EthereumTxInput;
use frame_benchmarking::v2::*;

#[benchmarks]
Expand All @@ -31,7 +31,7 @@ mod benchmarks {
let target =
H160::from_slice(&hex::decode("dfb975d018f03994a3b943808e3aa0964bd78463").unwrap());
// Calling `store(3)`
let input = BoundedVec::<u8, ConstU32<MAX_ETHEREUM_TX_INPUT_SIZE>>::try_from(
let input = EthereumTxInput::try_from(
hex::decode("6057361d0000000000000000000000000000000000000000000000000000000000000003")
.unwrap(),
)
Expand Down
2 changes: 1 addition & 1 deletion pallets/ethereum-checked/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ impl ExtBuilder {
assert_ok!(Evm::create2(
RuntimeOrigin::root(),
ALICE_H160,
hex::decode(STORAGE_CONTRACT).unwrap(),
hex::decode(STORAGE_CONTRACT).expect("invalid code hex"),
H256::zero(),
U256::zero(),
1_000_000,
Expand Down
10 changes: 5 additions & 5 deletions pallets/ethereum-checked/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,14 @@
use super::*;
use mock::*;

use astar_primitives::ethereum_checked::MAX_ETHEREUM_TX_INPUT_SIZE;
use astar_primitives::ethereum_checked::EthereumTxInput;
use ethereum::{ReceiptV3, TransactionV2 as Transaction};
use frame_support::{assert_noop, assert_ok, traits::ConstU32};
use frame_support::{assert_noop, assert_ok};
use sp_runtime::DispatchError;

fn bounded_input(data: &'static str) -> BoundedVec<u8, ConstU32<MAX_ETHEREUM_TX_INPUT_SIZE>> {
BoundedVec::<u8, ConstU32<MAX_ETHEREUM_TX_INPUT_SIZE>>::try_from(hex::decode(data).unwrap())
.unwrap()
fn bounded_input(data: &'static str) -> EthereumTxInput {
EthereumTxInput::try_from(hex::decode(data).expect("invalid input hex"))
.expect("input too large")
}

#[test]
Expand Down
1 change: 1 addition & 0 deletions pallets/xvm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ astar-primitives = { workspace = true }

[dev-dependencies]
fp-evm = { workspace = true }
hex = { workspace = true }
pallet-balances = { workspace = true, features = ["std"] }
pallet-insecure-randomness-collective-flip = { workspace = true, features = ["std"] }
pallet-timestamp = { workspace = true, features = ["std"] }
Expand Down
14 changes: 11 additions & 3 deletions pallets/xvm/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,11 @@ use parity_scale_codec::Encode;
use sp_core::H160;
use sp_runtime::MultiAddress;

#[benchmarks]
use astar_primitives::Balance;

#[benchmarks(
where <T as pallet_contracts::Config>::Currency: Currency<T::AccountId, Balance = Balance>,
)]
mod benchmarks {
use super::*;

Expand All @@ -38,10 +42,12 @@ mod benchmarks {
let source = whitelisted_caller();
let target = H160::repeat_byte(1).encode();
let input = vec![1, 2, 3];
let value = 1_000_000u128;

#[block]
{
Pallet::<T>::call_without_execution(context, vm_id, source, target, input).unwrap();
Pallet::<T>::call_without_execution(context, vm_id, source, target, input, value)
.unwrap();
}
}

Expand All @@ -55,10 +61,12 @@ mod benchmarks {
let source = whitelisted_caller();
let target = MultiAddress::<T::AccountId, ()>::Id(whitelisted_caller()).encode();
let input = vec![1, 2, 3];
let value = 1_000_000u128;

#[block]
{
Pallet::<T>::call_without_execution(context, vm_id, source, target, input).unwrap();
Pallet::<T>::call_without_execution(context, vm_id, source, target, input, value)
.unwrap();
}
}

Expand Down
67 changes: 45 additions & 22 deletions pallets/xvm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,19 +37,20 @@

#![cfg_attr(not(feature = "std"), no_std)]

use frame_support::{ensure, traits::ConstU32, BoundedVec};
use frame_support::{ensure, traits::Currency};
use pallet_contracts::{CollectEvents, DebugInfo, Determinism};
use pallet_evm::GasWeightMapping;
use parity_scale_codec::Decode;
use sp_core::U256;
use sp_core::{H160, U256};
use sp_runtime::traits::StaticLookup;
use sp_std::{marker::PhantomData, prelude::*};

use astar_primitives::{
ethereum_checked::{
AccountMapping, CheckedEthereumTransact, CheckedEthereumTx, MAX_ETHEREUM_TX_INPUT_SIZE,
AccountMapping, CheckedEthereumTransact, CheckedEthereumTx, EthereumTxInput,
},
xvm::{CallError, CallErrorWithWeight, CallInfo, CallResult, Context, VmId, XvmCall},
Balance,
};

#[cfg(feature = "runtime-benchmarks")]
Expand All @@ -58,8 +59,8 @@ mod benchmarking;
pub mod weights;
pub use weights::WeightInfo;

#[cfg(test)]
mod mock;
mod tests;

pub use pallet::*;

Expand Down Expand Up @@ -88,25 +89,35 @@ pub mod pallet {
}
}

impl<T: Config> XvmCall<T::AccountId> for Pallet<T> {
impl<T> XvmCall<T::AccountId> for Pallet<T>
where
T: Config,
T::Currency: Currency<T::AccountId, Balance = Balance>,
{
fn call(
context: Context,
vm_id: VmId,
source: T::AccountId,
target: Vec<u8>,
input: Vec<u8>,
value: Balance,
) -> CallResult {
Pallet::<T>::do_call(context, vm_id, source, target, input, false)
Pallet::<T>::do_call(context, vm_id, source, target, input, value, false)
}
}

impl<T: Config> Pallet<T> {
impl<T> Pallet<T>
where
T: Config,
T::Currency: Currency<T::AccountId, Balance = Balance>,
{
fn do_call(
context: Context,
vm_id: VmId,
source: T::AccountId,
target: Vec<u8>,
input: Vec<u8>,
value: Balance,
skip_execution: bool,
) -> CallResult {
ensure!(
Expand All @@ -121,8 +132,12 @@ impl<T: Config> Pallet<T> {
);

match vm_id {
VmId::Evm => Pallet::<T>::evm_call(context, source, target, input, skip_execution),
VmId::Wasm => Pallet::<T>::wasm_call(context, source, target, input, skip_execution),
VmId::Evm => {
Pallet::<T>::evm_call(context, source, target, input, value, skip_execution)
}
VmId::Wasm => {
Pallet::<T>::wasm_call(context, source, target, input, value, skip_execution)
}
}
}

Expand All @@ -131,26 +146,33 @@ impl<T: Config> Pallet<T> {
source: T::AccountId,
target: Vec<u8>,
input: Vec<u8>,
value: Balance,
skip_execution: bool,
) -> CallResult {
log::trace!(
target: "xvm::evm_call",
"Calling EVM: {:?} {:?}, {:?}, {:?}",
context, source, target, input,
"Calling EVM: {:?} {:?}, {:?}, {:?}, {:?}",
context, source, target, input, value,
);

ensure!(
target.len() == H160::len_bytes(),
CallErrorWithWeight {
error: CallError::InvalidTarget,
used_weight: WeightInfoOf::<T>::evm_call_overheads(),
}
);
let target_decoded =
Decode::decode(&mut target.as_ref()).map_err(|_| CallErrorWithWeight {
error: CallError::InvalidTarget,
used_weight: WeightInfoOf::<T>::evm_call_overheads(),
})?;
let bounded_input = BoundedVec::<u8, ConstU32<MAX_ETHEREUM_TX_INPUT_SIZE>>::try_from(input)
.map_err(|_| CallErrorWithWeight {
error: CallError::InputTooLarge,
used_weight: WeightInfoOf::<T>::evm_call_overheads(),
})?;
let bounded_input = EthereumTxInput::try_from(input).map_err(|_| CallErrorWithWeight {
error: CallError::InputTooLarge,
used_weight: WeightInfoOf::<T>::evm_call_overheads(),
})?;

let value = U256::zero();
let value_u256 = U256::from(value);
// With overheads, less weight is available.
let weight_limit = context
.weight_limit
Expand All @@ -161,7 +183,7 @@ impl<T: Config> Pallet<T> {
let tx = CheckedEthereumTx {
gas_limit,
target: target_decoded,
value,
value: value_u256,
input: bounded_input,
maybe_access_list: None,
};
Expand Down Expand Up @@ -210,12 +232,13 @@ impl<T: Config> Pallet<T> {
source: T::AccountId,
target: Vec<u8>,
input: Vec<u8>,
value: Balance,
skip_execution: bool,
) -> CallResult {
log::trace!(
target: "xvm::wasm_call",
"Calling WASM: {:?} {:?}, {:?}, {:?}",
context, source, target, input,
"Calling WASM: {:?} {:?}, {:?}, {:?}, {:?}",
context, source, target, input, value,
);

let dest = {
Expand All @@ -231,7 +254,6 @@ impl<T: Config> Pallet<T> {
let weight_limit = context
.weight_limit
.saturating_sub(WeightInfoOf::<T>::wasm_call_overheads());
let value = Default::default();

// Note the skip execution check should be exactly before `pallet_contracts::bare_call`
// to benchmark the correct overheads.
Expand Down Expand Up @@ -277,7 +299,8 @@ impl<T: Config> Pallet<T> {
source: T::AccountId,
target: Vec<u8>,
input: Vec<u8>,
value: Balance,
) -> CallResult {
Self::do_call(context, vm_id, source, target, input, true)
Self::do_call(context, vm_id, source, target, input, value, true)
}
}
Loading